As I wait for a docker container to restart for the 1001st time, an artifact of poor design decisions made on an old project I have been lumbered with, I can't help but feel the need to try to define in more clear terms what I have been referring to as "reflection time".

Reflection time is simply the time it takes for a developer to go from a location in an application (a button, an animation, an API call), to identifying where in the code that element is defined, changing its value, seeing those changes reflected in the application. For me this is the first crucial step in modifying an application, as until you can see your changes reflected you can't begin serious development work.

Say for example there is an issue on a webpage:

<div class='fooo'>malformed content</div>

The first time we attempt to modify the application the reflection time is made up of three parts, the identification, the modification, and the verification.

Identification: Inspect malformed element, search codebase for fooo.

Modification: Update fooo to foo, save changes to index.html, refresh browser.

Verification: Inspect malformed element, check that the class is now foo.

After these three steps you can now be sure that the code you are modifying and the application you are seeing are directly linked. You are now in control of the project and can make the desired changes you need.

This is of course an extremely simple example and the reality is that most projects will have a much more complicated set of steps to go through to be able to reflect changes. I hope to now offer some suggestions for things that architects can do to help give future developers the lowest reflection time possible; as well as offer some suggestions on how developers working on an issue with a long reflection time can hopefully reduce the time wasted by this cycle.

Identification

One theme through all of this is consistency, the more consistent a codebase, the easier it is to identify entry points and cut down on the reflection time. When you are debugging a piece of code that is of a unique structure, there is a significant overhead simply due to things not being where you expect them to be.

This step is less important than the next two, as when developing a specific feature you tend to only have to identify it once, where as with the other two steps, modification and verification, you tend to repeat them many times while developing. That being said identification can often be an especially time consuming task when dealing with spaghetti code, so here are some things to consider.

A: Architect - Someone making large design decisions about the project

D: Developer - Someone new to the project without knowledge of its history

One repository

A: It is important to have all of the code for your project in one repository (and ideally one main branch) for many reasons, it makes deployments, pull requests and code reviews much easier, but crucially it means that when searching for something in the codebase whether it is a commit or function name you are going to be looking in the right place to start with.

D: If your organisation uses github, you can search org:yourcompany <search string>, then hit the code tab, to find code across all repositories!

Unique names

A: Naming is arguably one of the hardest things in programming, take the time to consider how variables, functions, files and classes are named. It can be difficult, but resist the urge to have fun names; it might be a great in joke now, but three years down the line when new developers are wondering what on earth the "pineapple" module is doing, it isn't so funny. Perhaps add "sensible naming" as a step in your QA process, and remember consistency!!

D: When prototyping it is easy to think "I don't have time to worry about proper naming", and that is often the case; but make sure you go back and review the naming once your feature is complete.

Avoid hacks and deprecate

A: When an old module is going to be deprecated or removed, try to scrub it from the code base and update the docs to reflect that. If you still rely on legacy code clearly mark it as such with class and variable names, don't just name the new module the exact-same-library2.js.

D: Fight for time to refactor hacks into more suitable structures, that match the layout of the project. Fixing something with a quick hack, leads to unexpected spaghetti code.

Clear file structure

A: Pick a file structure and stick to it. Classes go in this folder, templates in this folder etc... It allows developers to quickly narrow down their search area.

D: Break code down into multiple files with clear names to make the filename more relevant. If you have file with 4000 lines in making up 100 different functions, no filename is going to be able to suitably describe that file.

Good commit & PR standards

A: Create and enforce a good standard for commits and pull requests formatting. For example if every pull request is on a branch that links back to a ticket or card, it's far easier to find out why a certain line was introduced or modified.

D: When committing be accurate and descriptive, make your commits small and concise. Don't forget you can search through a git log with tools like fzf or github. You can also search for commits with git log -S<string> to get historical context on a code snippet.

Effective free text search and navigation

A: Despite the best efforts in structuring an application it won't always be obvious where to look in project; so make sure that free text searches can be ran easily by including built and minified files in .gitignore or similar files. Also consider adding ctags files and having them regularly updated if your developers utilise them.

D: Make sure you have an effective and easy to use free text search in your IDE / Editor, it's invaluable for jumping around code. Shameless plug for my guide on using fzf & rg to make a really really quick search in Vim.

Modification

This step is important to focus on as developers will be doing it quite literally thousands of times on a project, so every millisecond here counts. Once the developer has identified where in the project they think the relevant code is, it's time to modify the application itself. Actually modifying the code isn't difficult, but once you hit save how long does it take from that point to the application being updated with your changes and in a working order? This is the modification time and you want to make it really really small.

Code watching

One of the easiest ways to speed up this time is to ensure that you have a script that is watching the source files for changes being made and will instantly start your build processes as soon as the developer hits save. It isn't always possible, but it's certainly worth looking into if you don't have a watcher setup already.

Reducing build times

I can't really say much about this, as every platform and project is going to have different build steps, but examine the build process for your developers, how long does it take for the application to build? Is it over five seconds? Personally when it gets much over five seconds my mind will tend to wander in the direction of reddit or other distractions, and suddenly what may be a ten second build time is actually a thirty second build time after I've fully enjoyed the latest GIFs the internet has to offer serious business emails I recieve.

Check that all the build steps are necessary for development, you could be using the same process for production as you are for development, which often has extra steps that aren't needed during development such as minification of libraries.

It's really worth investing some time into this, a project may last ten years, how many times will it be built, by how many developers? Seconds make minutes hours and days, so trimming one or two off of the build is so much more economical.

View refreshers

Once the app is built, do you need to close the old one and open the new one? Do you need to refresh the page, or make that new API call?

Part of the build should include a hook to refresh your view of the application, again I can't be specific here, but investigate if it's possible for your platforms. Ideally a developer can edit a line of code in the project, hit save and see the changes with no manual interaction required.

Verification

The final step in calculating your reflection time is how long it takes you to verify whether the changes you have made have actually changed the right feature in the application. This might just mean that you have added a print statement to a profile view, and when you load the profile view and see the new print statement, you know that the code you have found does in fact get ran on the profile view.

The issue here is when a developer needs to go through several steps to reach a point where they can verify their changes. A workflow might be; clear cookies, delete session, login, navigate to profile view, update password, see if cookie has changed. That is a whole bunch of steps to have to go through, when all we really want to do is the last one.

Unit tests w/ watchers

The best way to avoid long dependency chains of interactions is with good unit tests, as tests can be automated. If you have an existing set of tests for a feature, set them up to run when you save your code, and add any new tests you may need to verify that you are in fact working in the right area, save your changes and the tests will hopefully confirm or deny you are in the right place.

Then be a good programmer and flesh them out into real tests once you are done and revel in the glory testing brings to a project. They let you test just the thing you want to, and can be done at a keystrokes notice.

Developer modes

Having a mode where a programmer can enter a special developer session is also a handy way to remove restrictions that might slow down verification, and to include verbose debugging information.

It is good practice to have your local and production applications matching as closely as possible, however I would suggest making one aspect of the design, say the background colour, of the application different between the two versions. This helps to prevent two things, firstly developers going through the first two stages of reflection, identification and modification, and then going to verify it on the production application. This is far too easy to and can easily be avoided.

The other benefit of such a change is to prevent people accidentally operating on production data or including test data in a production environment. Not directly related to this article but it is still helpful!

Make everything scriptable where possible

If there are no tests for the project write some! When the boss doesn't let you, do the next best thing and script your verification. Simply making a quick and dirty bash script to send a request to an API or using xdotool to click the right sequence of buttons can only take a few minutes, but can make verifying your changes so much quicker and reproducible.

Conclusions

My definitions:

  • Identification Time = The time it takes to locate the code responsible for a feature
  • Modification Time = The time it takes to change that code and have the app updated
  • Verification Time = The time it takes to check that the changes have been reflected in the app
  • Reflection Time = Identification Time + Modification Time + Verification Time

Identification time is important as it pertains to how quickly can you locate bugs, and is often the longest of three. The way to reduce it is by being smart about the architecture of the project. Modification and Verification times are usually much shorter, and can be improved with better use of toolchains to reduce manual effort. They are also crucial to make as small and as consistent as possible as they tend to be repeated many thousands of times during development.

Reflection time as a whole is a great way to measure how friendly your code base is to work on; it is also a way to gauge experience with a project. Find a point in the application and ask the project lead to change a value in it and time how long it takes them. If you were then to then ask someone new to the project to do the same thing, how big is the time difference? If it is huge, consider why that is, did they have trouble understanding the file structure of where the code might be? Did the build fail for them as they were missing some undocumented library? Did the need to have a special development configuration to run that feature locally?

This is also an interesting way to see how complex different features are, does one feature set require a much greater level of knowledge about the system to be modified compared to another.

The main thing that improves all of these areas is consistency, something that can be achieved by enforcing clear standards, testing and QA practices. Enforce a linting ruleset that will stop someone naming a class with _'s in, making sure developers can't push code without the linter and tests passing. These little things make for a much nicer project, not just in reduced reflection time, but in increased test coverage and reduced regressions.

I Hope this has been of some interest, it is a concept I've always thought about and not seen a good term to describe it with so took the liberty to define my own.

:wq