[ Home | Library | Contents | Prev | Next ]


Bitwise Operator

by Matt Slot


Probably the most important thing a programmer can do to improve his code is to structure it better. It's not only a matter of convenience, it means faster implementation of new features, simpler debugging, long term maintainability, and portable code. It requires a bit of forethought, and an awareness of each portion of the whole project as it's constructed.

I'd like to discuss 3 different places to improve the structure of new and revised code: interface, implementation, and incremental changes.

Interface

When designing a new application or library, it's important to understand how each portion will interface with each other. I'm not talking about USER interfaces, but PROGRAMMER interfaces. Each service that an application uses or provides should be modular -- well-defined requirements and promised features.

The infrastructure routines should provide memory, string, and data storage (lists, trees, tables). Additional "modules" should provide file, database, and network access. The core application will then provide a layer between the low level services and the actual user interface (if there is one).

For many of the well-advertised reasons, object-oriented programming can simplify the programmer interfaces between various service modules and the application itself. Even without OOP techniques, modularized code makes it much easier to update, change, or even port such services because it "abstracts" these services and hides their implementation behind a simplified set of functions.

With this method, the library provide a consistent interface to the caller and the programmer is free to modify or replace the code that actually implements the promised features.

Implementation

The next suggestion for improving code structure promotes the use of "bottleneck" routines. These routines perform a narrowly defined service for the caller, such as sending commands over a network or reading an arbitrary data chunk from disk and dispatching it to the correct handler.

The term bottleneck derives from the fact that many functions will call this one particular routine, and this routine will dispatch the desired behavior to the appropriate implementation. Bottleneck routines are typically key programmer interfaces to a specific library or functional module, and either consists of the exact sequence of calls to provide the functionality or they provide a large switch/case statement that dispatches to multiple handlers.

The primary advantage to such routines is that they provide a single access point for a whole suite of related functionality. For example, what if the programmer wants to track exactly what data is being written to disk for debugging or just counting bytes. Consider an application that, each time it needs to write to disk, it opens the file, scans to the right location, writes the data, closes the disk, and checks for errors. It's pretty hard to debug the file saving code because it's spread all over the application.

On the other hand the programmer should write single routine that performs the entire sequence of steps, and then call the bottleneck each time data needs to be saved to disk. Later, it becomes trivial to set a debugger breakpoint to trace exactly which data is being written to disk (and from where in the project). It's also the perfect place to extend functionality, such as a byte counter or an option to save to the system clipboard.

Object oriented programmers often have some language support for writing bottleneck functions using "polymorphism". In the application, a number of classes can provide a "draw" member function that performs a slight variation of the same drawing functionality appropriate to the desired object. Adding a call to the draw member of the base class let's that function act as a bottleneck, to provide common drawing functionality such as coloring the pen or enabling/disabling drawing at a global level.

Incremental Changes

In the early stages of design and development, the programmer will try to bottleneck the code in as many places as possible for the reasons indicated above. This is typically planned at a very high level for the key functional and data structures. On the other hand, there are often many opportunities to improve the structure of the code as development progresses and more patterns become evident in the code.

Invariably, as existing features are fleshed and new features are added, certain elements or functional sequences tend to be reappear -- especially in "copy and paste" code. As a good rule of thumb, when I find myself writing basically the same sequence of code for the third time, I step back and try to find a way to bottleneck the code.

As I become aquainted with new library routines or write code to interact with something written by another programmer, it often turns out that the interface was written in a rather general purpose manner. This is often fine because there are often different ways or reasons to use a given piece of code, but when I start using that code I have a specific use or platform in mind.

Each time I program to that library's interface, I must write a bit of glue code to convert from my internal data format to the general purpose parameters that it expects. As my code depends more upon the new library, it becomes useful to create wrapper functions that simplify the conversion to reduce code bloat and bottleneck the functionality. Later, for example, if my code were to change it's internal data units or format, it would be straightforward to revise just the wrappers themselves and not every place they are used.

None of these techniques are well-defined rules for success, but they do provide a certain measure of structure and stability to code. Using descriptive names for the bottleneck and wrapper routines supplement programmer comments as a way to improve code readability and maintenance. Writing small utility functions not only aids debugging, but also saves from problems when writing similar code and missing a +1 in a certain case.

Well-structured application software and programmer libraries are often much more usable and stable than those that sort of accumulate new features without much forethought. It's also a sign that the programmer intends to maintain the code in the future, and didn't write it as a weekend hack.

Matt Slot, Bitwise Operator


[ Home | Library | Contents | Prev | Next ]