How to Create Robust Software Designs

One of the biggest issues in developing software is how to write programs that have robust design. Robust design, of course, can have different meanings, and won’t be the same thing for everyone. However, there is the intuitive notion that software that can withstand the test of time is based on robust design.

My experience in software development has shown that many of the common notions of robustness in software design are false. For example, the idea that software has to always be reusable is a major source of problems for software architects and developers. It is a fallacy that has been promoted by vendors of new technologies, and in some way continues to be accepted without criticism.


A Few Personal Guidelines

Here is a set of guidelines that I follow when writing software, and that have proven to be much more useful in generating programs of high quality.

Implement only what you need: one of the things I try to avoid is creating software that is more general than it needs to be. If you are trying to be ahead of the development curve, there is always the risk that you will be going in the wrong direction. This happens because we can rarely be sure of the future needs of your organization.

Avoid useless abstractions: this is related to the previous topic, but deserves to be treated separately. Abstractions are useful if they simplify the way a feature is implemented. However, some abstractions exist only to satisfy the needs of developers.

This happens a lot when people start to treat everything as a design pattern, for example. Clearly, even the simplest development problems can be solved with design patterns, but they don’t need to — specially if this is going to turn the program into something more complicated.

Make it easy to change your implementation: at the same time, implementations should be easy to change, or even swap completely, if possible. Big abstraction can make this task harder too, so you need to be aware of the repercussions of using a more complicated data structure.

Address the main issues, avoid what is non essential: one of the big laws of programming should be: avoid doing what you don’t need to. Many developers are tempted to add features that are non-essential, but “nice” to have. The problem with this line of thinking is that nice features also takes time to test and maintain. Testing and maintenance are the biggest costs in software development, not the initial time spent on developing the feature. Anything that we can do to reduce testing and maintenance is useful, specially if we are still meeting the requirements of the project.

Avoid using components that will require effort to understand/change: Finally, although it goes contrary to the wisdom of most project managers, there are cases in which you don’t want to use a component for a project — even if it already exists. Some components are not written to be reused, and trying to do so only make the project more difficult to manage. If that is the case, don’t try to reuse for the sake of it — it is just a pointless exercise if it is not improving the productivity of the team.


Further Reading

A book that explores many of these topics (presenting real numbers) is Facts and Fallacies of Software Engineering, by Robert L. Glass.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Startups and Raising Money

It is interesting to notice that when most media outlets discuss the world of startups, they mostly address the issue of fund raising. In some way, it looks cool, especially to magazines and newspapers, to brag about millions of dollars being raised from VCs by a new company. However, what is often not said is that raising money is usually a consequence of making a cool product, and not the other way around.

It is just like the way people look at successful people and see only the money they have. Money is only the consequence of success, not its cause. It is something that comes along with success. There is plenty of people that have large sums of money but unsuccessful (and unhappy) in their lives. By focusing only on the money, there is no way to understand the process that is really going on.


Focus on the creative side

I prefer the strategy used by some new startups. Instead of focusing on the funding part of the equation, try to put your effort on the product and team building first. This has two advantages: (1) it is a more relaxed way to work, since you are not putting pressure on the creating process; (2) it creates value immediately, instead of a debt that has to be repaid with future value.

Now, there is the issue of feasibility for this approach. It clearly depends on what kind of product you want to create. In more traditional industries, product development takes massive amounts of investment. This is not the same in software: one person or a small group can do a lot, if they are experienced and determined.

In the software industry, getting external funding may be more of a hassle than an advantage, because of all the strings that are usually attached to an external investment. Is that what you want to your starting company? Starting with a low profile reduces risk and increases the upside for the founders.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Tips to Read Other People’s Code

Reading code is a basic skill that working programmers need to acquire in order to work on development teams. It is really hard to expect that you will work in isolation of other people’s code. Therefore, reading code is a skill that can make you much more productive.

Although it not easy to learn this quickly, a few tips can help with getting the most of any piece of code. Some of these ideas have helped me to understand large projects over the years.

  1. Understand the program first: it is pretty difficult to understand code for a program if you don’t know yet what it does in the first place. You should try to get used to how the program works before digging into its code.
  2. Read with a goal in mind: the same way it is easier to understand a book if you are trying to answer a concrete question, a similar state of mind will work for reading code. Start with a specific goal and try to answer it. For example, a question such as “how is the screen repainted” can lead you to understand better what you are reading.
  3. Understand the conventions of the platform/language: the way code is organized depends heavily in the language used, and on the requirements of the platform. For example, the way C++ code for Windows is organized is different from C code for XWindows. If we talk about web applications, this is even more dependent on the framework used.
  4. Use a debugger to acquire dynamic information: a debugger can be of great help in understanding code paths that are difficult to grasp (especially in OO languages). Just put a breakpoint in part of the code you want to study. When the breakpoint is hit, check the call stack. This will show you how a piece of code was called and how objects communicate.

Further Reading

The idea of literate programming was promoted by Knuth in his book Literate Programming.

The Linux kernel is a classic example of code that has been read for its own qualities. Understanding the Linux Kernel is one of the many books that explore this fact.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

The Way a Program Starts in Windows

If you write a program in C/C++ for Windows, you are very close to the interface with the operating system. For this reason, it is also interesting to have some idea of what it is doing.

There are two main types of Windows programs: graphical programs and console-based programs. The way Windows determine what kind of program you are trying to create is by a flag it stores inside the executable. This is also called the “subsystem” in which the program will run.

To determine the subsystem in visual C++, one has to set an option in the linker section. This will determine what Windows will see as the subsystem for that program.

Depending on the subsystem, Windows will call one form or another of the C library function that starts a program. The name of this function is also configurable (usually it is __tmainCRTStartup), and it can be changed from the linker section of VC++.


First Steps of a Program

The initialization function in the C library does just a little bit of work that is required by the C run time. One of the most important is to initialize the heap, so functions like malloc and the operator new can work properly.

The initialization function also sets some common values used by the system, such as environmental variables, and the version number of Windows.

Finally, __tmainCRTStartup calls the main function declared by the Windows program. Usually this is called WinMain, but it can be some variation of this depending of the version of Windows and if you are using ANSI or Unicode.

When a program returns from the main function, the initialization function cleans up the heap and returns to the operating system.


Reference

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Understanding const pointers and variables in C++

In modern C++, we are used to see const pointers. They are a useful way to avoid changes in memory passed to a function. As such, they are very common in function declarations.

The const word, however, has other uses. It turns out that when the const keyword is applied to the pointer itself (instead of the contents of the pointer), the const modifier is also very useful.

When we say, for example, const Type *t, we are saying that the contents of the object pointed by t cannot be changed. However, we can just as well say Type * const t, which means that the pointer t is unchangeable.

For example, check the following code:

int main() {
   int i, j;
   const int *p = &i;
   // *p =0; // ERROR: cannot modify the memory pointed by p
   p = &j;

   int * const q = &i;
   *q = 0;
   // q = &j;  // ERROR: cannot modify the pointer q
   return 0;

   int const k = 0;
   // k = 1;   // ERROR: cannot modify the variable k

}

See that Type * const t may be a syntactical clue for how the code is organized, because in short methods we usually don’t want to have variables and pointers changing. Thus, in such a case we should avoid having variables that are not const.


Understanding const Syntax

The reason for the difficulty of understanding the behavior of expressions such as Type * const t is due to the complexity of the C++ syntax.

Notice that the example const int k can also be written as int const k (as shown above). This denotes that the const modifier is intended to the variable.

When we have a pointer, however, we can also have a const memory location, which is represented as Type * const p. The const attribute is now referring to the content of the pointer, and this is indicated by having it after the * operator.

To simplify the notation, a better way to write const attributes is to make this difference explicit as in the examples bellow:

Type const * p; // can't modify what p points to
Type * const q; // can't modify q itself
Type const * const t;// can't modify neither t or point value

As a quick tip to remember the correct syntax, always keep the const close to the * operator, to make its meaning easier to understand.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter