Sunday, October 14, 2012

Procedural or Class-based OOP for a simple python library?

Python is an OOP language. Everything in Python is an object. Functions are first-class objects which mean you can pass them around like objects. Try that in languages like C or C++. I am pretty sure you can do it (just google that on StackOverflow), but I am also pretty sure it would be very very very difficult to achieve. I actually asked a similar question at one point...

As a library designer, I am always seeking the best design patterns. I want my code to be clean, small and general. I don't want to make too much assumptions. I want it to be maintainable and easy to use.

One of the libraries we planned to release in the future as part of the Graphyte Project is called RepoMan. This libraries uses the Mercurial commands to perform init, clone, push, pull, add, addremove, and commit. My friend Jeremy did the core implementation and I did the external functionality such as write to a file, read a file, or rename a directory. 90% of what we did in this library were plain I/O operations using the os and shutil libraries. We didn't have a single class in the original design. Not at all.

Tonight I started implementing some hooks for SCM-Manager to talk to its REST apis. I used requests to communicate over HTTP. As usual, the first thing I did was did some initial coding and wrote a few lines of tests.

My initial codes were class-based. I thought about writing the entire thing in pure functions, but then I remember that our webclient will probably benefit from having an object instantiated and passed around, instead of having to reconstruct or saving those kwargs (stuff we need for the repo communication, such as username, password, repo url, etc) all the time.

Clearly, in that case, class-objects helps a lot. With functions, you really can't.You usually don't have a way to store the state. In fact, with functions, states should be one-time, and shouldn't be persistent.... closure is not a state-storage counterpart. Closure is just a form of lazy evaluation to me. Once you complete the function, the state is lost.

I thought about function vs class-object. I finally went with functions in my initial implementation. I realized that for my own libraries, the lowest level of the libraries (or the core) should be simple and straightforward. I could write these independent, individual functions and then implement the interface as I wish. Users can easily pull one of these functions and make use of it if they don't need a full-featured interface.

If I have all the REST apis written as functions, then I can make a very simple Python class-based interface that calls these apis individually. That interface is what people use. It should be simple to use. If I started writing all the cores into the methods, I am revealing the implementation. I am not abstracting it from the end-user. In the end, I ended up writing ugly codes in my methods.

I may be handling some logics after calling some functions in my methods, but that piece of logic code is specified to that method. If I don't like what I did for the core, I can easily killed the core and replaced that old function with a new implementation. My interface will usualy survice.

The conclusion is that when writing a library, stay focus on what the library intends to do. If the library is some complex data structure, the lowest level could be a data structure, which in C or C++ could be a class or struct. There is nothing wrong with that. But even in C or C++, the implementation and definition are always separated in best practices. Thus, the lowest-level of the implementation should be independent from the interface. Anyone who wish to use my libary can easily import a particular function (e.g. a user may be interested in reading the content of a file using the scm-hook). That import will give the user the full control of that piece of functionality.

Stop writing everything as classes in Python. Most of the stuff in Python should remain as functions. Classes usually make codes harder to debug and maintain.

No comments:

Post a Comment