What is a Good Program?

code-woods-3

How do you know if the software you are building is “good” software?  How do you know if the programmers on your team are “good” programmers?   As a programmer, how do we systematically improve ourselves?

The goal of most programmers should be to improve their craft of building programs.  A good programmer builds good programs.  A great programmer builds great programs.  As programmers, we need a way to judge the quality of the programs we build if we have any hope of becoming better programmers.

What is the problem you are trying to solve?

A traveler approaches a heavily wooded area he must pass in order to get to his destination. There are several other travelers already here. They are scrambling to cut a path through the woods so they too can get to the other side. The traveler pulls out a machete and starts chopping away at the brush and trees. After several hours of hard physical labor in the hot sun, chopping away with his machete, the traveler steps back to see his progress. He barely made a dent on the heavily wooded area, his hands are bruised and worn, he has used up most of his water, and he is exhausted. A second traveler walks up to the first traveler and asks him what he is doing. The first traveler responds, “I am trying to cut a path through these woods.” The second traveler responds, “Why?” The first traveler snaps back in frustration, “Obviously, I need to get to the other side of these woods!” The second traveler responds, “That wasn’t obvious at all! You see, I just came down that hill over there and from there you can clearly see that the wooded area is deep, but narrow. You will die before you cut your way through the woods with that machete. It would be much easier to just go around. As a matter of fact, if you look to your right you can see a taxi stand in the distance. He can get you to the other side quickly. “

As programmers, what is the problem we are trying to solve?

The first traveler lost sight of his goal. Once he encountered the wooded area with all of the other travelers already cutting their way through the woods, the problem went from getting to the other side to chopping down trees. Instead of stepping back to evaluate the possibilities and try to find the most efficient way to the other side, he joined the crowd that was already chopping away at the woods.

Programs are tools

Programs are solutions to problems. They are tools to help people accomplish their goals. Just like the first traveler in our story, programmers often lose sight of the problem they are trying to solve, wasting most of their time solving the wrong problems. Understanding the problem you are trying to solve is the key to writing good software.

Programs are tools designed to solve a problem for an intended user.

Good tools are effective. They solve the problem they were built to solve. A good hammer can hammer in nails. A good screwdriver can screw and unscrew screws. A good web browser can browse the web. A good music player can play music. A good program solves the problem it is supposed to solve. A good program is effective.

Good tools are robust. A hammer that falls apart after just one use is not a very good hammer. Similarly a program that crashes with bad inputs is not a very good program. A good program is robust.

Good tools are efficient. An electric screwdriver that takes a long time to screw in a screw is not as good as an electric screwdriver that can screw in screws quickly. Similarly, a web browser that takes a long time to render a web page is not as good as one that does so quickly. Good programs are efficient.

Like any other good tool, good programs are effective, robust, and efficient. Most tools are built to solve a well defined problem that is not expected to change. A nail will always behave as a nail does, thus a hammer’s job will never need to change. Programs, on the other hand are typically built to solve a problem that is not well defined. Requirements change all the time. A good program has to be flexible so it can be modified easily when requirements do change.

Good programs are flexible.

Creating flexible programs that can easily be adapted to meet changing requirements is one of the biggest challenges in software development. I stated earlier the key to building a good program is understanding the problem you are trying to solve. Programming is an exercise in requirements refinement. We start with an understanding of the fundamental problem we are trying to solve by using a plain language. In order to create a solution for the problem we start defining requirements. Some requirements are based on fact and some are based on assumptions. Throughout the software development process we refine those requirements, adding more detail at every step. Fully specified, detailed requirements are called code. The code we write is nothing more then a very detailed requirements document a compiler can turn into an executable program.

The challenge comes from changing requirements over time. Our understanding of a problem may change. The landscape in which we are operating may change. Technology may change. The scope of the problem may change. We have to be ready for it all.

When requirements change we have three choices: do nothing, build a new program, or modify the original program. Building a new program is a perfectly acceptable solution, and may be the right answer in some cases. Most of the time due to time and budget constraints, the best answer is to modify the original program. A good program will spend most of its life in production. During that time the requirements of the users and landscape is likely to change. When that happens your program no longer meets our first requirement for good programs: A good program is effective. The requirements of the problem no longer match the requirements specified in your code. Your program no longer solves the problem it was intended it solve. It is broken. You have to fix it as quickly as possible. If the program is flexible enough you can modify it quickly and cheaply to meet the new requirements. Most programs are not built this way and end up failing as a consequence.

To build a flexible program, a programmer should ask the question: “What requirements are least likely to change, and what requirements are most likely change?” The answer to this question will inform every aspect of your program architecture and design. It is impossible to build a good program without the answer to this question.

Good code?

Programs are made of code. Good programs are made of good code.
In this post I have specified the top-level requirements for what a good program is. In the next post I will start to refine these requirements further to answer the question, “What is good code?”

We're Hiring Engineers at Ladders. Come join our team!

Stateful Components in Clojure: Part 1

MG_blog_post2

You’re building a web app with Clojure, and things are going great. Your codebase is full of these fantastic pure and composable functions, you’re enjoying all the great ways in the Clojure ecosystem to manipulate HTML and CSS, generate data for your web APIs, and maybe you’ve even written a Clojurescript front-end. It’s all smooth sailing until you realize that it’s time to connect to a database…

If you’re like many Clojure developers, you’ve lost some time learning about various bad ways to solve the problem of managing stateful things like database connections, caches, queues, authentication backends, and so on in Clojure apps.

Let’s look at the hypothetical journey of one developer – we’ll call him Dave, since everyone knows a Dave – and his attempts to solve this problem.

Dave’s Journey

Dave is building a web app. Probably one that’s a lot like yours. And he too is just now running into the problem with stateful services, as he needs to add a database to his app.

Dave is first tempted to avoid the state problem entirely – a noble impulse for any engineer – by stashing the database config in a dynamic var, reasoning with some justification that it’s just data so that’s not so bad. He ends up with the following:

(def ^:dynamic *db-config* "postgresql://localhost/devdb?user=dev&password=dev")

(defn select
  [query]
  (jdbc/query *db-config* query))

This approach certainly has its benefits. None of Dave’s handlers have to know or pass around anything about the database; they simply call the select function. Dave is initially satisfied with this approach. It looks pretty easy, so he writes some tests.

(deftest homepage-handler-test
  (with-bindings {app.db/*db-config* test-config}
    (is (re-find #"Hits: [0-9]." (homepage-handler {})))))

It’s reasonably testable code, so long as he always remembers to correctly bind the *db-config* var. He keeps working on his code until he notices that some of his pages seem to be loading slowly, especially the ones that do a lot of database queries. Pretty soon the reason becomes clear – Dave’s app has to open a new connection to the database for every query.

The obvious solution is to use a persistent database connection. The question is where to put it? Using a dynamic var to hold the config is one thing – that’s just data – but a database connection is a stateful object. Like so many have done before him, Dave accepts a compromise on his functional programming principles and decides to stash the database connection in a delay. Now his code looks like this:

(def ^:dynamic *db-config* "postgresql://localhost/devdb?user=dev&password=dev")

(defonce db-conn (delay (make-pool *db-config*)))

(defn db []
  @db-conn)

(defn select
  [query]
  (jdbc/query (db) query))

Testing is now a bit tricker. He has to redef the db function and handle setup and teardown.

(def test-db-conn (atom nil))

(defn with-test-db-conn
  [f]
  (reset! test-db-conn (make-pool test-config))
  (f)
  (shutdown @test-db-conn)
  (reset! test-db-conn nil))

(use-fixtures :once with-test-db-conn)

(defn test-db []
  @test-db-conn)

(deftest homepage-handler-test
  (with-redefs [db test-db]
    (is (re-find #"Hits: [0-9]." (homepage-handler {})))))

Dave is feeling nervous. It’s a nagging, itching sort of feeling in the back of his mind, like the first stages of athlete’s foot. This code is starting to bother him. Just this little bit of global state is already requiring a lot of machinery to work with. His worries haven’t erupted into a full scale breakout yet, however, and he’s got more story points to complete.

His next task is to add a new feature that’s so important that his boss’s boss’s boss is regularly asking for status updates from his boss’s boss, which get translated into urgent requests to his boss, which finally become exhortations so anxiety-ridden that they are nearly incoherent to him. Their web app is the premiere service for posting pictures of cats wearing socks, and Dave needs to add a feature that either watermarks the pictures of these unfortunate animals with the site’s URL for non-paying users, or with a user-selected message for paying users.

To do this he needs to build a system that reads from a job queue in the app’s database and processes each new image to add the appropriate message. He’ll need to have several workers doing this for performance reasons, since few things have less patience than a cat forced to wear socks. This is a tricky problem for his approach to state management because he needs to be able to initialize and shut down these workers. Not only that, but they depend on the database connection.

Here’s what he comes up with.

(def ^:dynamic default-message "Posted to www.mssngvwls.io")
(def ^:dynamic number-workers 3)

(defonce stop? (atom false))

(defn start-workers []
  (reset! stop? false)
  (dotimes [worker-n number-workers]
    (future
      (while (not @stop?)
        (if-let [task (select-next-task (db))]
          (do
            (add-watermark (:image task) (or (:message task) default-message))
            (complete-task (db) task))
          (Thread/sleep 500))))))

(defn stop-workers []
  (reset! stop? true))

He adds a call to start-workers to his main function, and it seems to work well enough. He wasn’t able to do much iteration at the REPL while developing because he kept needing to manually restart things, and it was tedious to keep things pointed at the right database. His test code needs to redef even more things now. There’s still the database redefs from before along with the custom setup and teardown testing code, and now he also has to redef the stop? atom so that he can test this worker code that uses a completely different start and stop mechanism.

Dave’s morale is down, too. Nothing about this code seems functional anymore – he may as well have done it in Java. It just feels wrong to him, but he doesn’t see a better way out. He’s reached the low point in his journey. What Dave needs is a hero, and one arrives in the unlikely form of Stuart Sierra.

Continued in Part 2 and Part 3

We're Hiring Engineers at Ladders. Come join our team!