It's just a button

In the beginning there was a date field.

GUI date field in GUI

Changing the date would display the tasks that were recorded on that specific day. The code worked and life was good.

The project manager comes in one day and says: “Sloth, our customers would like to display recorded tasks for a certain time frame and not for one day only. Our competition is breathing down our neck, we need this feature badly. Make it happen. Yesterday”.

Seems like a reasonable argument. Adding another date field is a breeze.

Two date fields in GUI

You add a new listener on the new date field and add some logic in the model. If either of the date fields changes, one of the listeners will be triggered and the tasks of the chosen time frame will be fetched from the database and displayed in the UI. The manager is happy with the feature and you can go back to daydreaming and drinking coffee. Deep down you fear the day of opening that file again and adding more features on top.

A couple of days later a project manager storms into your office: “Our customers love the new feature for displaying tasks. It’s a bit cumbersome to use though. Say I want to select only the tasks from yesterday, or only the tasks recorded in the previous week. In such case I have to manually change both date fields. Can’t you like add a button or two with some predefined time frames to make this software easier to use”.

Uh oh, not this thing again. But here we have important people expecting things to move fast and break if necessary so you go long: “Sure, that shouldn’t take too long”.

Who has the time to update the design document for such an urgent feature request is what you are thinking while adding the buttons to the UI. Everything is hardcoded, but the UI is almost done. Localization will be tackled later on.

Two date fields and quick date selection buttons

Who is changing the language to anything else than English nowadays anyway. In all these years, have the users not learned that localized software is always broken. Unless you want to lose the rest of your hair when searching for the correct menu option in half broken half localized software that doesn’t match with the documentation, it’s better to just leave it in English.

Writing the button logic was simple. Look how neatly it works you say while clicking around. Let’s check what happens if I load tasks for the entire year.

Fans of your computer starts spinning and the UI freezes. What’s going on here you wonder, while making a mental note to fetch the data from non UI thread. Who thought that was a good idea in the first place?

You put a breakpoint in the model and retry. Breakpoint hits once. Data is being pulled from the database and displayed in the UI. Seems like a correct behavior. Continue. Breakpoint hits second time, same data is being pulled from the database. Huh, that should not have happened.

Inspecting the code reveals the problem with the date field listeners. Whenever a date field value has changed the data is fetched from the database and rendered on screen. Changing the start date field will do that. Changing the end date field will do that as well. Obviously the new feature buttons are triggering the fetch data behaviour twice as they change both start and end date field one after another.

Project manager creeps behind your back and sees you playing with the buttons on the screen: “Great work, I see you have a sense of urgency. You are almost done with this feature.” Uh yeah, I guess. The buttons are not really working, but the UI is kind of done.

A brilliant idea comes to your mind. You add another internal state called “batchDateChange” and rewrite your button change listeners [1]:

yesterdayButton.setOnAction(event -> {
    batchDateChange = true;

    // ...set start/end date fields

    batchDateChange = false;
    dateChanged();
});

void dateChanged() {
    if (batchDateChange) {
        return;
    }
    // ... rest of logic
}

Recompile, launch, try again. Problem solved. Time to push this change for a code review. Time spent: 1 day. The project manager will crown you as the greatest developer that ever walked the earth. That sweet sweet promotion will come anytime now.

The code review result comes in 3 days later: “Sorry I did not have the time to review this sooner, but apparently your change affects the table of tasks which is displayed in another tab. That table was never designed for displaying more than 10 tasks at the same time and now it looks all messed up. If we want this feature merged, we have to fix that part of the UI as well.

We all know who that “we” will be. You open up your editor and dig into depths of the code jungle. The original programmer truly kept Clean Code advice close to their heart.

Every if statement argument is neatly extracted into its own small single responsibility method. Instead of seeing the logic right there, you have to scroll around in the editor to build a mental model of the program and its explosion of methods. Luckily there are no stale misleading comments in there. In fact there are no comments in there at all. All you see are hairy branches with zero explanations why all of that crazy logic is there for. Wasn’t that supposed to just load the data into the table?

The name returned by the git blame does not ring the bell. You ask around. The original developer is supposedly long gone. Great. You are on your own.

You start commenting the unknown sections of code and inlining all hundreds of one lined methods with expressive names such as “isInvalid”. Parts of the logic seems to be repeated, so you extract them back into helper functions. Tens of exceptions are being thrown from a single method. You throw all of them away by pressing this neat key called delete. It’s getting dark outside, it’s far past your usual leaving time. You keep on typing, there is no turning back now.

On the 2nd day of your refactoring crusade a project manager comes along asking: “How is this new feature going, it should be done by now.” Nope, still working on it.

A week of development later you are finally finished. During this time you have:

  • added a few buttons on the screen
  • refactored and commented the broken table UI logic
  • added the missing unit tests that discovered a bug in another component
  • moved all IO interactions that freezed the UI into a non UI thread
  • wrote a command line utility for inspecting missing fields in localization files which was so far done by hand by some intern that is no longer working here
  • fixed a failing continuous integration build that was broken for months and nobody cared about
  • helped to onboard the new hire
  • fixed a styling issue of the label that was overflowing on every blue moon that just happened to be on the day when you were testing your changes

The project manager was furious: “That was supposed to be an easy fix. You’ve spent 2 weeks when all I asked you was to put a button on the screen. What took you so long?”

It’s never just a button.

Notes

[1] It’s probably a better idea to replace such boolean state with an enum. It usually makes adding another such field to the group of circular wired fields easier in the future.