Developer as a Service for better management
TL;DR While in many industries it’s common to identify the different activity types performed by a professional during the project lifetime, in the grounds of software development it’s often disregarded, or at least misunderstood. Don’t get me wrong, there are probably lots of engineering books on this matter, but in comparison I’ve seen very little being said from the day-by-day developer’s perspective in the form of a cookbook.
I’m sure that most advanced (senior) developers already know this through experience, so -probably- nothing new for them here.
But if you are either a novice, solo or freelance developer, a technical team leader, or a confused client struggling “to get something useful out from a developer”, then maybe the following insights help to shed some light on how we developers do our job.
I try the approach of “developer as a service” in order to devise a “boxed service” nature and bring more awareness on what we developers do on duty (and why). I believe that having a better situational awareness helps reducing mental and technical debt on the long run. It’s ok if you are a solo developer and want to borrow some technical debt to speed up things (however, if the project scales, there will eventually get more people involved). While working on a team, here some hints.
Developers are totally capable of (somehow) producing code upon zombie keystrokes. But if we can’t be aware of what those actions are, we can’t manage them.
When I think of the first times I got a freelance gig, the negotiation would usually go something like this:
Client: I’m looking of an engineer programmer webmaster electrical senior+++++ to do stuff. Lots of stuff.
You: Ok, what kind of stuff?
Client: Stuff! Can you do stuff for me?
Client: Cool. So can you build a website for me?
You: (aha… there the stuff) Sure! What kind of website do you need?
Client: A website. Simple stuff. Can you do that?
One month later…
You: Here your website, sir.
Client: Thanks. It looks nice, but.. where is Tickie Dilly?
You: What “Tickie Dilly”?
Client: Tickie Dilly the unicorn was supposed to brew my coffee. I had this idea in my mind all the time. So where is it?
If we take this conversation and think it in terms of “developer as a service”, it would pretty much look like this:
website = developer.doStuff(client.getRandomData());
website = developer.doMoreStuff(client.getRandomData());
/* I heard something about infinite loops */
Yes. The infamous and bloated doStuff() single entry point of any novice developer.
Let’s go for a ride and take a deep breath.
Some background on what other industries do
I’ve always admired how aviation industry get stuff done.
Compared to more domestic fields, it has a great advantage on process optimization, management, regulation, redundancy, reliability, problem management and -fundamentally- communication. Perhaps, because the lives of many people depend on this, this industry needs to stand out.
Of course, as any other industry, there exist unavoidable human error, corruption, and unsolved problems. But if we take a closer look on their nature, we see that:
- human error is planned and managed as a risk
- corruption has more to do with political structure and economics than engineering itself
- most unsolved problems are just the crest of the wave on human evolution
So what I’d like to do today is bringing some awareness on how the signatures of these activities look like in order to identify them, because -as we said- if you can call them by their name, you can manage them.
Let’s now think of how to purchase a flight ticket in “flying as a service”.
- List flights(origin airport, destiantion airport, departing date time, return date time) : flights
- Pick flight(flight id) : flight
- Pick a seat(seat id) : flight
- Pay(flight id) : confirmation
Wow! It looks pretty much like calling API methods. It’s called “commercial aviation”.
Sure! You probably want to change some methods, perhaps add some parameters, whatever. Then, you got the idea.
“ So to manage them, you need to call them by their name ”
The very same way, the pilot flights the plane in a very API-like fashion. If we think of the entire trip as a project, we can identify clearly defined phases and procedures.
Do ground checkup()
Taxi to runway()
Taxi to gate()
Do you see more functions/methods? Do you think them as algorithms/procedures? Can you imagine their inputs and outputs? That’s the idea. Great!
This means that the pilot “do stuff” call responds to an asorted list of steps, each of which has it’s own procedure, with pre-conditions to be met and post-conditions to be expected.
A pilot won’t roll the runway if the “takeoff checklist” did not take place or any precondition item is missing.
Could you imagine a pilot performing two takeoffs with no landing, and a taxi to gate simultaneusly?
It not only makes no sense, but would also result in an obvious crash.
However, it’s a common practice for novice developers to fix a bug, add a feature, change the entire UI, pull in a new package and set a new config update, everything in the very same move! (This could also be happening on the same single commit.)
It’s understandably OK that a client or a manager would not see the difference and thus will ask for all those on the same request. But it’s the developer’s responsibility to communicate accordingly and proceed in a prolix manner.
“Well, it’s strange that it just deleted your holiday photos. I just changed the font color you requested and fixed a little bug on save method.”
How to communicate accordingly?
Another common mistake we developers carry out often (hopefully just on early days), is thinking that the source code or the resulting application by itself is a (the) message.
Since communications are mission critical in aviation, let’s grab another example I love from it.
[ Springbok 210 heavy aborts takeoff before rotating (lifting). Every time something goes off the normal plan, the Tower Control may request “say intentions” ]
SAA210: Cleared for takeoff, caution the wake turbulence, heading 320, Springbok 210 Heavy.
SAA210: Springbok 210, stopping on the runway.
IAD TWR: Springbok 210 Heavy, roger. State your intention and what’s the nature of your emergency?
SAA210: We had an engine 1 failure, so we’ll be vacating on the next one, Springbok 210 Heavy.
IAD TWR: Springbok 210 Heavy, do you need any assistance?
SAA210: Negative, not right now; Springbok 210.
IAD TWR: Springbok 210 Heavy, roger, turn right there at QUEBEC-4 and then a right turn onto QUEBEC and once again, say the nature of difficulty sir.
SAA210: Turning right here and right onto QUEBEC confirm.. and what was the last?
IAD TWR: I’m sorry, sir, yes. That’d be QUEBEC-1 and right onto QUEBEC, and what was the nature of your emergency there?
SAA210: We had an engine 1 failure.. engine 1 fault… but the aircraft is fine.
IAD TWR: Okay! Springbok 210 Heavy, I understand engine 1 fault. Right turn onto QUEBEC, contact Ground on 121.62, sir, for further.
SAA210: 121.62, Springbok 210 heavy, bye bye sir!
This is because in aviation protocolized communication is not superfluous, and being clear and explicit is mission critical. Even when it is blatant that the plane would not go, the next steps are still requested to make sure that plans are aligned and proceed accordingly.
Doing so is about having situational awareness. If we only thought of the overall project goal, sure, we want to just fly to destination. But in order to do that, you need to be fully aware of where you are now and which step is next.
The same goes for software development. The source code is a resulting artifact, but there are many other artifacts that help to anticipate and state intention. These are the design specs, documentation and automated tests, to mention a few. Unfortunately, these are usually skipped for the sake of more velocity or cost reduction, with catastrophic results.
Why so many and so diverse assets, then? Isn’t that too verbose?
Well… it is verbose. But that’s exactly the purpose: to be redundantly clear, explicit, and enable different languages for different roles intervening on the project.
- Technical Documentation is better understood by technical managers.
- User Documentation is better understood by the users and clients.
- Design Specs is better understood by the architects, analysts and developers.
- Automated Tests is understood by the computer and developers. It states intention on TDD and also confirms after productive code is written.
When all these artifacts say the same thing in different “languages”, it confirms that all the parties have got aligned intentions.
One time is trivial. Two times confirms. Three times and above is redundant. But being redundant is also more failure-tolerant.
Let’s take our developer class and try to devise some of the several activities that he may perform “as a service”:
Create the project()
Add a feature()
Fix a bug()
Change application behavior()
Add a test case()
Fix coding style()
Update configuration keys()
Update configuration values()
Commit code changes()
Push code changes()
Again. Sure! You want to add methods, set their inputs and outputs, perhaps add submodules? Of course, a developer can do a lot more and you may model this to your will. Great! That’s the idea!
The SOLID developer
Think of each action as a function, or an API method call, which has a set of input parameters and an expected output.
Since they are not the same, it then makes sense to isolate them into their own scope. This is kind of applying a bit of the S.O.L.I.D. principles to the developer job itself.
Just a quick note on principles. Use them wisely, as long as you get a real benefit from it. Always thrieve for what best work for you and your prospects.
The preconditions, postconditions, inputs and outputs
I like thinking of each method as a “boxed service” for which I can state the conditions to be met before I start the task, and what conditions my team and I should expect afterwards. Similar thing for the inputs and outputs, as of what I may require in advance and then deliver.
Let’s jump into an example.
I have a very simple application I just started. I’m requested to add a feature to send birthday notification over SMS. So as I start writing the code to send a birthday greeting, I realize that the application actually can’t send any SMS just yet. So I pull in a package to add this capability (which results to be just another feature), but after installed I realize that it does not work because my framework is outdated. Yeah, I have postponed this many times. So I start the framework upgrade. You know the story; The configuration layout is now different so now some other packages stopped working, so I need to fix them. Suddenly, everybody is wondering why sending a stupid greeting is taking so long.
You may think, “yeah, but I need to do all that anyway to get it working”.
If I was requested to add a notification feature, why should I upgrade the entire framework on the same move? Let’s also rephrase it to see a different meaning. If I was requested to upgrade the framework, why would I fix a package that was already broken? Because you can’t keep moving if you have a broken package. That’s correct! But then it’s about identifying dependencies and doing the right things in the right order.
This is invisible to most clients and managers, because software is like an iceberg. They only see the features and the UI, which happen to be just the tip of the darn ice cube.
How could we re-work this scenario? I will point out a simplified example to have a grasp of the idea. Feel free to cook your own “service boxes” or “API calls”.
- Add feature: Send birthday greeting notification over SMS.
- Input: Feature specs, user story, docs, test cases.
- Output: Updated production code, passing tests.
- Preconditions: Requires feature to send SMS.
- Postconditions: You can now send birthday greetings.
- Sidenotes: We have only updated production code and perhaps some templates in the scope of a feature (./src and ./views/templates). We are not expected to change ./framework or ./packages in this move. Why do that??
- Add feature: Send SMS.
- Input: Feature specs, docs, test cases.
- Output: Updated production code, passing tests.
- Preconditions: Requires package to send SMS to be installed.
- Postconditions: You are now able to send SMS.
- Sidenotes: We have just bootstrapped the clientside of our application to use a package. We are expected to alter files in ./config, and probably nothing else.
- Install package: SMS sender.
- Input: Docs, selected package.
- Output: Updated codebase with installed package.
- Preconditions: Requires framework version X (I have currently X-1)
- Postconditions: SMS package is installed.
- Sidenotes: We just updated packages.json and packages.lock, and got the ./vendor dir updated. Why would this operation need to alter other packages or the framework?
- Framework upgrade: Upgrade to version X.
- Input: Selected target version.
- Output: Production code updated.
- Preconditions: Requires all packages to be up to date, requires platform version to be compatible, verified compatibility, checked package changelog, checked upgrade guide.
- Postconditions: Framework is now up-to-date.
- Sidenotes: We have only upgraded the framework. We expect ./framework to be updated, maybe some interfaces in our ./app got updated. But why would we deliver an upgraded framework that can now send birthday notifications?
- Package upgrade: Upgrade package.
- Input: Target package version.
- Output: Upgraded package.
- Preconditions: There exist a new package version.
- Postconditions: Package is up-to-date.
- Sidenotes: We are expected to change ./vendor or ./packages and maybe a packages.*, for example. But we are not expected to touch any framework file in this move. Maybe some ./app files need to be updated because of new interfaces. It’s up to you. The task can be called “Package minor upgrade” or “Package major upgrade” and you now get an idea of their different scopes.
For all these cases I’m assuming that we already have the repo set up. There are cases on which we are asked to do this into a new project. Why would one assume that I can push changes to a repo that I don’t have access to? Setting up a repo is not trivial. And even if it was, well it’s not trivial the time it takes and the money I charge for it, specially if any of the required input happen to be missing.
I hope this helps to grasp the notion of service scope. I know that it looks like we now have a lot more work to do. But it’s not. All those tasks were always there, but you are not going any faster by ignoring them. What makes more sense to me is the fact that clients and managers usually only see value on features and UI, but not in all the other tasks that are actually required.
If I was to add a feature, why would I be expected to fix a bug that was already there, for the same quoted price, for the same planned time? Wouldn’t it make more sense to first fix the bug, secondly write regression tests, then add a feature?
Think of this, why is flying to another country so expensive? I just bought a seat number!! There are lots of procedures taking place for you to be able to pick that seat and land in that city. You don’t need to know the inner work of an aircraft, the pilot experience, the route planning based on airspace fees and fuel cost, the airport schedules, the emergency patrolling, safety and security regulations, and lot more “suff” taking place. You just see the time it takes and the price you pay.
Some software development may be cheap. But good software isn’t. As we thrieve to make it affordable, we cannot sacrify either results nor life quality for this.
For next article I’ll craft a simple framework based on the real world usual activities I perform as a developer, hoping to serve as a template for those who find it useful.
Do you have any boxed service to suggest? Comment below.