Note: In this post, I’m using the terms „behaviour“ and „structure“ to describe more „dynamic“ and more „static“ properties of a particular system or piece of software. That is, I’m using the term „structure“ for properties like „Java is a statically-typed language“ or „We follow Java Beans convention in this project“. On the other hand, „behaviour“ to me are things like „The application correctly calculates SHA-512 checksum“ or „The calculations are done within 10 seconds“. To put it differently, „behaviour“ answers the question „what“, and „structure“ answers the question „how“.
Do I care how the developers structured their code when I’m trying to buy a train ticket online? No! Do I care in which language my IDE is written? Of course not! Do I care which SSL library my mail client uses? I couldn’t care less!
What I do care about is, that I can find the best train connection between A and B, choose any additional options I might want and buy the ticket. From my IDE I’d require things like the ability to compile the project, run the tests or highlight the code among other things, not that it’s written in language X or that’s written in a particular style. Same for my mail client, it needs to reliably and securely send and receive emails, show them to me, enable writing new messages and not much more. How those systems are achieving their goals is usually not important, that they do achieve them is.
What is quite often bothering me is that we’re not focusing on the behaviour, but on the structure. I could see that in one of my last projects, also on two different levels.
On a code level we had to, for example, implement a verification mechanism for user-provided data. External users might enter some data which our employees need to approve or reject, partially or completely, and in case of rejection also provide feedback. I admit, the process was not trivial and there were some corner cases. From a technical perspective, the data were owned by one app and had to be provided via REST API to another, clearing app.
The first thing the team did was to prepare the data structures „necessary“ to prepare the API. Merge request affected 81 files with 2479 lines added and 336 removed. Then the API itself with 14 files affected, 697 lines added and 70 removed. And then at least 7 other merge requests adding or fixing related stuff. We had no idea what would we need to build to make the system behave as we wanted it to. Funny fact, from the beginning some of us were raising concerns about the code design, we even discussed that and started refactoring this part of the system. In the end, we gave up on that, really improving things would take too much time.
My point is this: we were creating code we thought we’d need. I’m afraid that quite a lot of this code was not necessary, or, to make it usable, we had to adapt it (see YAGNI). Instead, we should have started with the UI and written just enough code to make the UI work. We already had the designs for the UI so we knew how the system should behave. In the end, we might have ended with the same amount of code, maybe even with the same code, but then we would be sure it was all really necessary because it served the UI. At least I would question the amount of the code, and the way it currently looks, far less.
But the same symptoms I see on a higher level: stories. They speak with nouns, not verbs. We need to prepare for our web app, for example, pages X or Y, where X or Y are the names of pages in design documents. I’d rather see more of „enable approving data“ or „enable rejecting data“. It’s not that bad in this case, because a single page is about one or more behaviours, and behaviours are usually not split across pages. But it gets worse quickly, because „prepare page X“ is only about the frontend, „prepare API“ or „prepare repository“ are separate stories, and, usually, support both pages X and Y. As I mentioned above, we’re not starting from the top and going down, we quite often start somewhere in the middle, like on the API level, and then build up and down. That way we start writing new functionalities without having clear behaviour in mind.
This approach has some benefits though, I admit. The first one is, it’s like in most other projects, at least those I was working on. It’s familiar, and although this is usually a good thing, I was already writing about the dark sides of it here and here. Moreover, it might be easier to parallelize work on stories cut more „horizontally“. In the case of more „vertical“ stories, the ones going across all layers, from UI to DB, there’s a higher likelihood of code conflicts if two people were to work on topics that overlap each other to some extent. Also, the first developer to work on such a story would need to invest a bit more time in it to provide some basic infrastructure, create some controllers or repositories, expose endpoints, etc. Anyone following would probably just need to extend this infrastructure.
Still, if this infrastructure would be so small, that it would be „just“ enough to fulfil the requirements, do not try to predict and support possible future use cases (YAGNI anyone?), it should not be a problem. This post is not supposed to be about TDD or BDD, but here we are, they fit the situation like a glove. Behaviour-driven development provides us exactly with the tools we could use to focus on the important part, the behaviour, and avoid the pitfalls like „writing too much code“.
I also mentioned „vertical“ stories. Would I go too far if I compared this approach to self-contained systems? In the end, the goal is the same, to have everything required to fulfil a requirement, big or small, in one application, from UI through business logic down to data persistence. Or cross-functional teams? Again, a team should have people with all the skills necessary to provide value to the customer, including development, sales, marketing, operations, etc. The same is for stories. Once a story is implemented the system should provide new or changed functionality (behaviour). A half-baked „API is there, now we just need to adapt the DB and UI“ brings no value. Or, more correctly, provides no measurable progress. The users, in the case of UI or web apps, cannot see anything, there’s no value for them, and they also cannot provide us with any feedback. If, on the other hand, you could show them even rudimentary UI covering the simplest use case, you are already giving the customers something tangible. Even if this use case is too simple for the customer to use, you could at least ask them if that’s the direction you should be going in.
Things like performance, reliability, security and other -ilities are also behaviour, not structure. I want my application to be performant and deliver the results to the users within a reasonable time. What is a reasonable time depends of course on the context. With reliability, I want to make sure, that even under heavy load, my application will still be working. If a system my app depends on fails, my app will still be working. Same for security, I don’t want my application to reveal sensitive data to unauthorized users. At the same time, I want to make sure, that authorized users always can access their data.
Finally, I need to stand in defence of the structure.
It is important, even extremely so, but we can only judge it by looking at the properties it provides.
For example, in Java, I might choose a LinkedList
as a data structure to hold some data, but I should not do that just because I simply like the way list elements are linked in memory.
I might find this particular implementation of a collection the best fit for my needs because I don’t know how many elements I’ll have in that collection, but I know I’ll be working near one of the ends of this list.
Based on this use case I might find that this is the fastest data structure I have at my disposal.
In other words, it does its job better than other data structures given some quality criteria, like performance in this example.
Similarly, we would be choosing to store user passwords not in clear text, but using some hashing algorithm. We would be doing that not because, for example, we like the idea that all passwords would then have the same length, but because this would prevent others from getting to know the passwords or access to the system in case of a data breach. We would use an algorithm with salting to, again, prevent others from trying dictionary attacks against possible leaked passwords.
And again, we’d be architecting our system in a completely different way if it had to only serve at most 10 parallel users (think of some internal, non-critical system used in a small company) when compared to a system that might need to serve 10 million parallel users (GMail? Office365? Facebook?). In the first case, we’d most likely need just one instance of it, a simple database behind the scenes, all that running maybe even on a desktop PC sitting under your desk. In the second case, one instance will definitely no be enough, the same goes for any supporting system, like a DB. Moreover, you’d most likely want to make sure it’s always available, has good response times, etc., etc. Running this „under your desk“ is definitely not the best option.
Whenever we choose a particular structure out of some options, we base this decision on what this particular structure has to offer. If we choose the structure wrong, the behaviour of the system using that structure might be suboptimal. It may be too slow if we used bubble sort for example. It might run out of memory if we choose an in-memory DB and there’s just too much data. It might also be easier to further develop and maintain the system if we choose the structures right. And so on, and so forth. Choose your structures in such a way that they serve the desired behaviour best.
TL;DR
The users want our applications to do something. For them, it’s irrelevant how we achieve that. In particular, the structures we choose to use. Therefore, next time when you’re asked to implement something, make sure to first understand what’s the desired behaviour of the app. Based on that try to make the application do that. It is only now that you should think about structures.
I’d like to thank Joachim and FND for providing feedback and helping me making this post better.