loading...
Cover image for Hacky and Clean Programming

Hacky and Clean Programming

danielkun profile image Daniel Albuschat ・5 min read

When I do programming, I do it in either of two "modes". Each of these modes serves a different purpose and I use them at different stages to achieve a goal. It is very important to be aware of the current stage you are in, because programming in the wrong mode can be most harmful for either productivity or code quality.

I call these two modes the "hacker mode" and the "clean mode".

Hacker mode

In "hacker mode", I try out either new technologies or architectural ideas. For example, when your current goal is to write a library to control a Bluetooth device, I would first go into "hacker mode" and try things out and figure out what would be the most efficient and most stable way to use the raw Bluetooth API.

When hacking, I do not care for code quality. I may violate my own guidelines, use ugly casts, name variables "x", "foo" or "data". The uglier, the better. Because I will definitely throw that code away. This is a very important aspect that should not be violated. You have to be aware that you will throw the code away, and you have to actually do this in the end. If what you hacked somehow does not work out, throw it away and start with a new hacking session - and throw this one away, too, once you learned the important aspects. No clean code. No docs. No comments. No unit tests.

And if you are afraid that, when re-writing what you have hacked before, the Second System Effect (as described by Fred Brooks in The Mythical Man Month) might kick in, I can assure you that with this method I have not yet ever experienced this. I think the Second System Effect applies to larger scales only, and only when putting effort into producing high quality in the first system, too.

After I hacked away and figured out which calls must be made in which order and know the parameters for best results and found a good structure for the API and the implementation, I sit back, have a good look at it and memorize the important parts. Then I stash the code aside for later reference and mentally mark it as "To Be Deleted".

Now I can switch to "clean mode".

Clean mode

I am programming in "clean mode" when I have a good mental model of what I want to create. When I know the important key parts of the implementation and have an idea of how the API and the architecture should look like. Often this information comes from a hacking session. Sometimes it is just there because I have been thinking about the problem for days, months or even years and now finally decided that everything is clear and can be put into code.

Most of the time, when programming in clean mode, I am doing DDD - Documentation Driven Development. Don't worry, this is not the new hip paradigm that you missed. It's essentially TDD, but I am writing the documentation of the code even before writing the tests.

Most important: The Docs

One major argument why TDD is cool is because you get "a feeling how your code works out when it is put into actual use". The same argument goes for writing docs. (Important note: I am talking about documentation that describes the functionality and properties of a class/function/etc. I am not talking about inline code comments, which describe details of the implementation). When writing docs, I always try to state the important details, not the obvious. When thinking about the non-obvious, you will learn whether the overall design is slick, or does have a few rough edges. With docs and tests combined, you can be pretty sure that the design is sound and that it works out well in practice. You usually even come up with new test cases this way.

There are a few, easy rules to follow when writing docs. This is especially important when writing them before the actual implementation.

First, it must be clear what the purpose of the element you are documenting is. If there is not one core function, but multiple tasks that are accomplished, you are most likely doing it wrong. Describe the core function of the element in one sentence, i.e. in a @brief. If the core function of the element can be unambigously deduced from it's name, you can skip this part.

Second, state the preconditions that must be met to use the element. The less preconditions you can name, the better. If there are no preconditions that are not enforced by the type system, you are doing it right. If the type system is not strong enough to express the constraints of the input parameters, document them in detail. The rumor has it that Haskell has an awesome type system, but unfortunately I have had not yet a chance to use it for productive work.

Third, and maybe most important for maintenance, state the side effects that may happen and which conditions they may likely happen under. If you are a really good programmer, you are writing functional code and can skip this part. You simply do not have side-effects.

Additionally to these, the standard rules obviously apply: Describing each parameter in detail (again: not the obvious!), possible exceptions thrown, how the return value has to be interpreted, etc.

While I do this, I always have two aspects in mind:

  1. What actual use does the element have for a potential user and which goals would she target?
  2. How will I possibly implement this element?

And it is very important not to get distracted by the implementation and create a bad API that does not resemble the tasks that a user of the class want it to accomplish, but instead just wraps the underlying technology. But it is also important to not create APIs that can not possibly be implemented in any reasonable way, or the performance will suffer considerably. You should avoid leaky abstractions for (nearly) all costs, but there is often a limit to this. (This is often worth prototyping in hacky mode.)

When I am done writing the docs, I begin writing the tests and see how my idea of how the API might be put to use actually performs. I use the docs that I have written and the side-effects and corner-cases that I have described to derive test cases and therefore get a pretty decent coverage.

I often draw some rough pseudo-UML to visualize the dependencies and relationships.

When actually implementing the functionality, I apply all the lessons that you have learned from Clean Code. I am aware of the docs and update them, when necessary. This may also lead to redesigning the API and therefore the tests. I am consciously taking the risk that implementation details that do not fit into the API are costly, because I have experienced it many times that this approach is worth it, since it results in well architectured, maintainable, clean and most importantly easy to use code.

Final notes

As stated in the beginning, it is most important to distinguish these two modes of programming and apply the correct one in each situation. Also, I would advice against using a mixture of both. Do not write hacky code in a clean code base and do not write clean code while hacking. It is just not worth it, because you either harm your code bases quality or are less productive than you could.

Only do hacking in fresh, isolated code bases that are drilled down to the minimal. This of course requires you to isolate parts and think of good, small components to build your software in, which is valuable in itself.

Discussion

pic
Editor guide
Collapse
ghost profile image
Ghost

"I Don’t Always Test My Code. But When I Do I Do It In Production" :D

Collapse
danielkun profile image
Daniel Albuschat Author

Nah, it's more about distinguishing learning from creating. I've seen all too often devs putting horrendous effort into code, design and tests when they didn't know the underlying tech, dependencies and quirks yet, leading to constant refactoring along the way - which actually leads to bad quality if you are not willing to throw away all your design. And it's still more effort than necessary, you could do better when first experimenting with no regard to quality. And after learning, throw the experiment away.

Collapse
mistermocha profile image
mistermocha

How do you know what's "obvious" when deciding what to document?

Collapse
danielkun profile image
Daniel Albuschat Author

Great question! And a question that you should ask yourself and your teammates every time you are not sure.

Here is a fictional example:

public void Print(Point position, string text)
{
   
}

What would be obvious and what would not be obvious about this function?

It obviously prints text at position.

What is not obvious: Is there a maximum length for text? What happens when position is outside of the canvas? Will an exception be thrown? What happens when position is inside the canvas, but text will flow outside? Will it be wrapped?
What color is text printed in? What font face, font size, etc.?

And, since C# and Java are one of the stupid languages that mostly-always allow null for everything even when it makes no sense at all, you should state what happens when either of the argument is null. (Incidentally, System.Windows.Point is a struct in C# and therefore does not allow null, yay!)