Continuing on my thoughts on being a pragmatic programmer I spend some time discussing how to ensure you are solving the right problem. In conjunction with that, I dive into my experiences with wizard code, a topic we cover more in detail later on. Next, I discuss design by contract and its relation to assertive programming. Continuing, I describe how designing to test can be utilized to prevent you from having to refactor your code to make it testable. The last topic I cover includes the benefits of orthogonal software components. If you didn’t read the previous post in this series I recommend doing so but it is not required.
As a software developer it is your job to solve problems. If something feels too challenging or even impossible it is probably because you are solving the wrong problem. Take a step back and consider that there may be another way to solve the current problem. Are you thinking inside or outside of the box? Where is the box located? A problem may seem difficult because you were unaware of the boundaries of the box. Perhaps, you did not realize that a library was capable of solving a specific problem for you so you rolled your own solution and it was challenging or perhaps, you were unaware that the problem occurs so frequently that someone on your team has already solved it for you. Always consider that a problem may not be impossible but that you may be solving the wrong problem.
Often we find ourselves so far removed from our domain, solving a problem that is a sub-problem of our current task that it causes us to ask for help on questions that don’t need to be solved. Imagine you are trying to solve problem X but to solve problem X you need to do Y, although, you get stuck solving Y so you ask for help with regards to problem Y. This is referred to as an XY problem. Problem Y was never the problem you needed to solve.
When asking a question I would recommend phrasing your question in a way that mentions both X and Y. One option might be, “I am trying to solve X, I have tried Y but it is not working, any ideas?”. This way, you expose the person you are requesting help from to the source problem and the way you are trying to solve it, either they know a better way to solve it or they can help you figure out why what you are trying is not working. When answering questions the first thing I would recommend doing is asking a clarifying question to ensure you are helping them with the right question. An option for that might look like the following, “What exactly are we trying to achieve by doing X?” They may respond by telling you that they are actually trying to do Y or they clarify their question further.
I am a big fan of libraries and frameworks if they solve the right problem. Often if you don’t end up using a framework you will end up writing your own (and it will probably be worse than something maintained by a large community of other developers). I don’t want to build an HTTP webserver from scratch before I can develop my RESTful API. That is a domain where people have spent considerable amounts of time writing boiler plate and infrastructure code for you, why waste time writing it yourself. The same could said for many types of applications such as User Interfaces. I think you could say almost every code base is using some sort of library to help solve a problem that has already been solved. Look for pre-existing solutions before implementing your own, odds are, if it is a common enough problem it has already been solved.
As great as libraries and frameworks are, there is a catch. The Pragmatic Programmer discusses the concept of “wizard code” which the authors define as code that magically solves the problem you are attempting to solve or at least helps you solve that problem. I would classify most libraries and frameworks as a form of wizard code. They solve a problem for you and you don’t necessarily need to understand how they solve that problem for them to be useful. The problem with wizard code is that it does a lot for you and if you don’t understand how it works it may be impossible to maintain an application built with wizard libraries or frameworks. If you don’t have some idea how a specific library or framework your using is working, how could you be expected to add new features or maintain existing ones? You may not need to understand exactly how a library works but you can benefit a lot from knowing a little. In Soft Skills by John Sonmez he mentions learning the 20% you need to be 80% effective with a technology. I find that to be a successful strategy with learning a new library or framework, you may never get to 100% proficiency but you will get close as you continue to work with the codebase.
The problem with libraries and frameworks is that we can end up treating them like black boxes. We give them some input like our data and they magically render our User Interface. What are we supposed to do if the UI doesn’t quite look right or if it renders slowly? We seemingly have no control, it is just a black box after all. This is in fact not the case, third party code is typically not a black box if it is open source. Don’t be afraid to read third party source code. This will allow you to have a more maintainable codebase because you will be able to diagnosis issues as something that is definitely your source code’s fault or something that is a problem with the library itself or more often how it is being used. Don’t let the libraries or frameworks you and your team are using be “wizard code” instead you should strive to understand how the technology works under the hood.
Design by contract and assertive programming are complementary concepts. I discuss design by contract in this post but for those that haven’t read it, design by contract is the concept that every software component has a contractual obligation that it must uphold which includes specific pre and post conditions. I firmly believe that you will see an increase in maintainability if you document a contract for every piece of code you write. In conjunction with that, the maintainability of your product will sky rocket if you are being an assertive programmer with regards to your contract.
Why should you even bother documenting software contracts? Imagine you call a function to calculate the square root of a number, what does that function do if a negative number is passed in as an argument? Does it return an imaginary number or does it throw an error? In the parameter level documentation if would be worth documenting the behavior of this edge case. Further, it is worth documenting all edge cases of all software components you write. Does this function operate on negative values, what about null values, does it throw an error I need to catch? All of the answers to these question can make up a contract that can be documented that you can refer to when you need to know what behavior you need to be able to handle when you call the function in question.
We alluded to it a bit earlier but it is important to enforce contracts. Enforcing your contract for your software component can be relativity simple. If your contract documentation states something like
throws an Error if parameter number is negative then enforcing that contract is easy, the first thing your function should do is check if
number is negative, if it is, then it should throw an error. It is up to you what the result of your edge cases will be but they should be one hundred percent consistent with the contract you have documented. The practice of design by contract and assertive programming makes your code more maintainable because people calling your code know exactly what types of return values, exceptions or side affects they need to be able to handle.
I am assuming that you have some familarity with software testing concepts such as unit testing and integration testing. In conjunction with that, I am going to assume you have already taken the plunge and believe that testing your code is generally the right thing to do. With that in mind, I am going to be crafting a rebuttal to “…but my code is too hard to test!”. We’ve all heard it, or even said it, how do you respond? I would go as far as to say if code is too hard to test it is potentially written poorly.
There are some things that are actually hard to test. If your code relies on network or file I/O it may be hard to test because you don’t want to actually make network requests in a test environment, your tests should be able to run offline. Although, in my post about focusing on functional purity I discuss how you can isolate I/O into a single place that can be mocked to always do something good or bad depending on your test case. In conjunction with that, In Java (and probably other OOP languages), code that utilizes static methods is tricky to test at times because static methods can not easily be mocked. A trick I learned is to put static calls into a wrapper method on the class under test and then you can spy that method to change its behavior and essentially mock what it is returning. It is not a perfect strategy and I welcome ideas for better strategies but it works well when you are only using a few static method calls. Considering I/O and the usage of static method calls is going to allow you to be able to code to test and allow you to skip the phase where you need to refactor your code to make it testable.
Classes that utilize composition of other classes can be tough to test. When you go to test the desired class you may quickly realize things are much more difficult than you had anticipated. The class has a method that calls a method on a different object which calls a method on a another object and so on. You don’t really want to test that call chain for unit testing purposes, but how do you get around this? A test constructor is a constructor you only use for testing purposes to pass in mock (or spy) values for the objects that compose the class you would like to test. This allows you to define what should happen when other objects are interacted with, allowing you to focus on testing the class you had originally wanted to test instead of wrestling with all the pieces that class is composed of.
When thinking of code that is not easy to test we may find ourselves considering just not testing it. Code that is designed to be tested will never be hard to test. If your code is already written in a manner that makes it testable you are more likely to test it. Finding the balance between designing your software, implementing it, and designing it to be testable is the key.
An orthogonal application does not have coupling between unrelated pieces of the application. The database that is being used should not be coupled to how your view layer is rendered. A more detailed example with regards to user interfaces is the capability to swap out your view layer if it is sufficiently decoupled from your model layer. In this case I would say the view layer and the model layer are orthogonal to one another. Developing an orthogonal application will allow for an increase in productivity and a decrease in risk. If you know you can make a change to a software component without it affecting other components you can more confidently make changes, allowing you to add new features more quickly.
Considering orthogonality at varying levels of your application’s implementation hierarchy is important. What pieces should be orthogonal to one another? The front end and the back end of your application should be able to vary independently. What pieces of the front end should be orthogonal to one another? In most cases, the model and view can be orthogonal to one another. As you can see, you can continue going deeper and deeper until you are considering the orthogonality of the classes (or functions) within your application. Always consider orthogonality of software components while designing and implementing. You may realize your application is so orthogonal that it allows you to compose different components in ways you did not originally imagine. Consider for a moment that you are building a web application where the back end is decoupled from the front end and your boss or client says they want a mobile application now instead. How do you respond? “No way!” or “Not a problem!”, hopefully the latter. If your back end is truly decoupled from your web application’s front end then it should be no problem to build a mobile application based user interface that utilizes the same back end as your web application’s front end. Orthogonal systems allow for composition of software components and better response to unforseen changes. In conjunction with that, they are more intuitive to work with since making changes are more predictable.
The most enjoyable part of working on an application that was designed and implemented in a way that considered orthogonality is the freedom. When all software components have documented and enforced contracts you have the freedom to change how an existing component is implemented. As long as it abides by its contract correctly you can confidently change the implementation and you know it will not effect any other piece of the application. Going further, an orthogonal system is more testable because you don’t have to account for any side effects in other components when testing. A well tested and orthogonal application is liberating to work on. You will have the freedom to swap out pieces of the application as if they were lego pieces. You can do this confidently because you have contracts that are enforced and test coverage that exercises those contracts in an automated manner.
When all of these concepts begin to intersect the quality of product being produced increases. Designing by contract forces you to always consider orthogonality. A system that was designed to be tested allows you to leverage the benefits of orthogonal software components to write better tests which in turn allows you to make changes to your software components with confidence. The synergy of all of these practices increases productivity and reduces risk when making changes.