During mid of last year I dug myself into the OS development rabbit hole on Youtube and in my fall vacation I had some slack allowing me to hands-on hack together my own Operating System called JOS (Jakob’s OS). This was the starting point of a pretty exciting journey. Till today, there is no particular purpose or goal other than learning more about low level basics. Nonetheless, I enjoyed this exercise so much that I can even grant it some therapeutic effect during very stressful business life 🙂 Of course, as a family father and engineering manager, there is not much time and I often found myself pondering in my mind while doing chores how to solve the next stack corruption puzzle, instead of actually coding/debugging. In this article, I will tell you a bit about my key learnings/technologies this exposed me to.
The starting point was a brief video series by David Callanan on how to write a bootloader for an OS till printing a Hello World to a console. I started to fork his repo and worked my way from there. First thing was to rewrite the whole OS code (besides the bootloader) in Rust, as this was another thing I wanted to learn. It took me a while to get used to Rust, but eventually came to a working Hello World. The next steps were minor adaptions to the console printing ecosystem. The first real big step digging into the intrinsics of x86_64 CPU instruction set was the implementation of interrupts. This took a great amount of debugging, trial and erroring and reading of many specifications and internet resources. From the latter, I want to highlight the AMD64 Architecture Programmer’s Manual, Volume 2, and Philipp Oppermann’s Writing an OS in Rust. The latter is really good for the first steps with one caveat: It is based in big parts on the author’s library, which abstracts all x86_64 basics away and is in many parts more a tutorial to use the library. I didnt want to use this library because my impression was that it masked too much of the interesting stuff how things actually work on the low level. Hence, I implement everything on my own terms. After adopting keyboard and time interrupts I used the latter to display the clock time on the console in the top right corner.
A huge help was that I could use the qemu emulator and the gdb debugger in combination (via VS Code’s extension native-debug). It was the first time using qemu and gdb as a developer and after getting beyond the initial learning curve I genuinely enjoyed debugging with it. As additional tools I also used Ghidra (disassembler) and ElfViewer (inspect executables).
A really huge next step for me was to introduce userspace programs and later on multiprocessing (running multiple userspace programs „in parallel“ with a simple round robin scheduler). After studying basic patterns for this, I chose the hard path to implement it mostly by myself. This is the part which I am most proud of so far, because this required me to derive many inner works from the specs directly and it has cost me a long time to get right. For weeks and months I fought with sporadic stack corruptions and CPU exceptions. As userspace program I implemented a simple Hello World program, which interacted with the kernel via syscalls. Due to lack of a file system, which to date I was for some reasons not eager to implement, this userspace progam is stored inside the os executable and loaded from there – a hack which is actually really cool. Then I added a vga mode which enabled the userspace programs to print colored pixels to the screen (one line for each of the 4 userspace programs):
OK so here we were, but what you gonna do with an operating system which has keyboard input, can run user programs, has vga output? Yes, you are correct – we run Doom on it 🙂
You probably heard of people getting to run Doom on all sorts of awkward devices, engineers made it to run on potato batteries and toothbrushes:
So if they can run it on such devices, there must be a way to run it on JOS, right? And yes, its possible. It required me to write a small C wrapper around PureDOOM, which in turn also made me translate my Rust-based libc to C. After adding some additional syscalls, fighting with the 6 bit color maps, malloc implementation I finally made it. So here we stand today, JOS runs DOOM.
As written before, I really like the regular updates provided by Thoughtworks in their Technology Radar. My focus is on the applicability of techniques, tools, platforms and languages for automotive software, with a further focus on embedded in-car software. Hence, I am ignoring pure web-development and machine learning/data analytics stuff which usually makes a huge portion of the whole report. Recently, its volume 27 has been published. Let’s have a look!
As usual, lets start with a dive in the „Technologies“ sector and its „Adopt“ perimeter. The first entry we can find is about „path-to-production mapping„. Its as familiar as it sounds – many of my readers will have heard about the Value Stream Mapping or similar process mapping approaches. Thoughtworks state by themselves that this one is so obvious, still they didnt cover it in their reports yet. Sometimes, the simple ideas are the powerful ones. I can confirm from my own experience that a value stream map laying out all the process steps and inefficiencies in an easy to digest manner is a good eye opener and can help to focus on the real problems instead of beating around the bush.
Something very interesting for all the operating systems and platform plans in Automotive is the notion of an „incremental developer platform„. The underlying observation that „teams shooting for too much of that platform vision too fast“ is something I can confirm from own experience. Engineers love to develop sustainable platforms, but underestimate all the efforts required for it, and management with its impatience is further undermining platform plans. Following the book Team Topologies‘ concept of a „thinnest viable platform“ makes sense here. Not shooting too far in the first step, but also treating a platform product as an incremental endeavour.
Another one which strikes me is „observability in CI/CD pipelines„. With the increasing amount and complexity of CI/CD pipelines in one project, let alone a whole organization, many operational questions arise. And operations always benefit from clear data and overview. Recently, a then-student and now colleague and me designed and realized a tool which enables CI/CD monitoring for more than one repo, but for a graph of repos. I hope we can publish/open this project anytime soon.
In the platforms sector, Backstage entered the „adopt“ perimeter. The project is actively developing forward, and indeed could be an interesting tool for building an internal sw engineering community.
Looking at the tools sector, I liked Hadolint for finding common issues in Dockerfiles.
As written before, I really like the regular updates provided by Thoughtworks in their Technology Radar. Since the new version #26 was released a few weeks back, I found now the time to put down my notes. My focus is on the applicability of techniques, tools, platforms and languages for automotive software, with a further focus on embedded in-car software. Hence, I am ignoring pure web-development and machine learning/data analytics stuff which usually makes a huge portion of the whole report. Let’s go!
In the techniques section in the „adopt“ circle we initially have „single team remote wall“. In a nutshell I think they mean having a dashboard showing the essential data, kpis and tasks for a remote development team. I think the trick here is the „single“ as I assume that most remote teams have dashboards, however usually multiple ones loosely coupled. In my current team, our Scrum Master has created a great Jira dashboard showing some essential data which could give hints at the team’s performance.
The second noteworthy technique is „documentation quadrants“. Referring to documentation.divio.com/ this provides a nice taxonomy of different documentation types. This is very relatable, as I very often experience a fuzzy mixture of all those types scattered in many places. Certainly this is something I will bring to my work network’s attention.
Third, we have „rethinking remote standups“. This follows a general observation that conducting remote daily standups in the same duration and content like the were recommended in former times (e.g. the typical 15 min Scrum daily) does not provide the same amount of alignment within a development team. This is not necessarily because of the the meeting itself, but because other casual sync occasions during the day are happening less in remote setups. In the radar, its recommended to try an extension to one hour, and of course the goal is to decrease the overall meeting load by this. I am thorn on this one, as I was always a fan of crisp daily meetings, avoiding random rambling on topics concerning only parts of the team. Blocking 1 hour for everyone every day sounds like an overshoot approach.
Next there is again the „software bill of materials“ topic. This is currently a huge topic in the software industry, there have been very concrete examples recently (e.g. the Log4Shell or NPM package events you probably read about). Tool support to transparently and consistently managing the used software in a bigger project is really needed. While in the web and cloud business there is a growing number of tools, in the embedded world there are only some puzzle pieces. I can currently think of some Yocto support for this, however this covers only Linux parts in usually more complex multi-os automotive ECUs.
„Transitional Architecture“ sounds like a promising thing, even though the radar’s description stays a bit vague. Luckily there is an extensive article by Thoughtworks‘ Martin Fowler on this approach. In my opinion, managing legacy software in complex setups is one of the key challenges in the whole software industry, even more so in automotive embedded software, which is characterized by the co-existence of decades old technologies with state of the art approaches. Formalizing the transition from on older architecture to a newer makes sense, as usually this transition is often not architecturally covered as extensively as a the target architecture. This leads to misunderstandings, hacky workarounds and other unwanted side effects to a sustainable development.
Going one circle to the outside, in the „assess“ perimeter, we first find CUPID. Aimed at replacing the SOLID rules with instead a set of properties of „joyful code“, it has some interesting observations and paradigms. Currently I only skipped over it, I think this deserves more time and maybe a dedicated article. However, I can recommend to check out the well written original blogpost by Dan North.
In the „hold“ perimeter we see „miscellaneous platform teams“. In contrast to „platform engineering product teams“ described earlier in the radar, this is kind of a degradation form. If a platform team fails to define a clear product goal and identify its customers, usually the scope becomes (or is) very fuzzy, leading to a unclear platform system. Hence, its strongly recommended to avoid this by achieving clarity of what actually is the scope of the team.
In the platforms sector, I could only identify one relevant blip „GitLab CI/CD“. Recently I see a lot of discouragement of using Jenkins, and of course if you use already Gitlab for its other elements (code hosting, code review, issue tracking) you may as well use it for CI/CD pipelines. For sure its better integrated in the overall Gitlab experience. However its just another vendor-specific DSL, so I wonder if there will be practical standardization on the pipeline definition soon.
Looking at the „Tools“ sector, I found the reference to the two code search tools Comby and Sourcegraph. Besides offering code search and browsing using abstract syntax tree analysis, they are also offering semi-semantical batch changes, enabling „large scale changes“. Comby is an open source tool, while sourcegraph is commercial. I think I will try at least one of them soon.
After some years of private tinkering with CI/CD workflows for web development, and a good load of professional exposure to embedded projects working hard to get CI/CD in a scaling, fast and reliable setup, I wanted to combine both. Earlier, I did some trial-and-error-project leveraging the great NodeMCU boards, but it was without any automated testing and no ci pipelines used. So it was time to make a step further in my private endeavors and setup a CI/CD pipeline with automated flashing and testing of new embedded code. An important requirements was that the automated testing should test the complete embedded device consisting of its hardware and software in completeness (black box test). Hence, only the „official“ outside interfaces like serial interface and physical output (LED!) should be used for automated tests. Of course, the used hardware, software and complexity in no way match what our projects‘ engineering teams handle every day, and I don’t intend to compete with the engineers. Its an exercise for myself to learn.
Without further ado, lets have a look at the setup:
We see the following: My vserver-hosted Jenkins is the same as usual. However, as local node connected to the target device (NodeMCU), I am using a Raspberry Pi Zero 2 W. I connected it as a node (formerly called slave) to my Jenkins master. The NodeMCU target is connected via a USB cable to that Raspberry Pi. The Raspi is able to build the code, flash it to the NodeMCU and run some tests written with Python. The NodeMCU target has an LED (with resistor) connected, which is controlled by the embedded software. To close the circle, the Raspi also has a BH1750 light detector board connected. The idea is: Whenever a software change is built and flashed, the Raspi can automaticall test if the LED is correctly lit, and if not, fail the test, hence the overall pipeline.
Eventually, with some vacation-breaks I made this work. Yay! There were, however, some impediments to overcome. You can find them here in case you would ever have a similar endeavor 🙂
To build the software on the Raspi, I had to install on it the according framework. After great experience with it on the desktop VSCode extension, I learned in the docs that PlatformIO also has a headless cli client. How to get it onto the Raspi? First I thought using a docker container would be a good choice, to have a reproducible environment. However, it was really hard to find any working, up-to-date docker for that. Hence, I finally decided to got with PlatformIO’s super-handy installer script.
Getting the LED to work with some simple code was not as straightfoward as I hoped after studying the docs initially. I could not make the LED light up at 50% no matter what I did to the values or wire connections. It was always at full power (which would be generally fine but not with the „product“ I had in mind, more on that in a later blog post I guess). I even used my simple osciloscope. Finally, it turned out that all the tutorials which explained this dead-simple setup had one snag: the provided value range had beed changed as a breaking change quite recently. After adapting to the new range it worked!
The BH1750 light sensor is connected with an I2C connection to the Raspi, which was my first own engineering exposure to I2C ever. In my first attempt I could connect it to the NodeMCU successfully, but after some travel and trying the same via the Raspi failed miserably. Again a lot of trial-and-error, until some random guys with the same issues on the interner hinted towards the pin connection on the BH1750. Indeed, it was extremely sensitive and the pins were always a bit off. I finally soldered it together and then it worked like a charm.
Using a Raspberry Pi Zero 2 to build the embedded software doesn’t sound very proper, and indeed a clean build takes a while and mostly blocks the complete machine for other things. The good news is that PlatformIO offers a simple out-of-the-box build cache solution, which requires not more than configuring a build cache directory, which is subsequently used. Of course, caches should always used with care and for reproducible build they should probably be turned off. Again, in my very simple setup it does its job.
So, as usual I spent most time debugging unexpected snags than on the actual concept or missing pieces. Still learned a lot.
I still have some open to-dos and followups:
Of course, my current „product“ is dead simple. Having the basics in place I want to extend the functionality gradually. With this minimal CI setup I can add/modify functionality without introducing regressions on the existing functionality.
Improve the pipeline duration. The average pipeline takes about 1 min, of which the flashing consumes the majority (30-40 secs) of time. Also there is a significant waiting time in the beginning which Jenkins does not include in the pipeline duration. I need to investigate what that is about.
At the moment the software is updated via USB cable. The NodeMCU framework also offers software update over the air (OTA). Leveraging that would some more flexibility in placing the device, and of course enabling Continuous Deployment on „production targets“. A first step I am considering is adding OTA as a second flash procedure, followed by another test run.
Exploring more options of NodeMCU’s and PlatformIO’s frameworks/tools. While setting up the above, I saw a lot of interesting things worth to investigate further.
Here is the (shortened) Jenkins log for reference:
Push event to branch master
Looking up repository jakob/NodeCI
Querying the current revision of branch master...
[...]
[Pipeline] Start of Pipeline
[Pipeline] node
Running on rpizero in /home/jakob/jenkinsnode/workspace/Jakob_NodeCI_master
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Declarative: Checkout SCM)
[Pipeline] checkout
The recommended git tool is: NONE
using credential [...]
Fetching changes from the remote Git repository
Fetching without tags
[...]
Commit message: "added acceptace test for physical led"
> /usr/bin/git config core.sparsecheckout # timeout=10
> /usr/bin/git checkout -f
[...]
[Gitea] Notifying branch build status: PENDING Build started...
[Gitea] Notified
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] sh
+ /home/jakob/.platformio/penv/bin/pio run
Processing nodemcuv2 (platform: espressif8266; board: nodemcuv2; framework: arduino)
--------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/nodemcuv2.html
PLATFORM: Espressif 8266 (3.2.0) > NodeMCU 1.0 (ESP-12E Module)
HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
PACKAGES:
- framework-arduinoespressif8266 3.30002.0 (3.0.2)
- tool-esptool 1.413.0 (4.13)
- tool-esptoolpy 1.30000.201119 (3.0.0)
- toolchain-xtensa 2.100300.210717 (10.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 36 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Retrieving maximum program size .pio/build/nodemcuv2/firmware.elf
Checking size .pio/build/nodemcuv2/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM: [=== ] 34.3% (used 28088 bytes from 81920 bytes)
Flash: [=== ] 25.7% (used 268009 bytes from 1044464 bytes)
========================= [SUCCESS] Took 7.07 seconds =========================
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (flash)
[Pipeline] sh
+ /home/jakob/.platformio/penv/bin/pio run --target upload
Processing nodemcuv2 (platform: espressif8266; board: nodemcuv2; framework: arduino)
--------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/nodemcuv2.html
PLATFORM: Espressif 8266 (3.2.0) > NodeMCU 1.0 (ESP-12E Module)
HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
PACKAGES:
- framework-arduinoespressif8266 3.30002.0 (3.0.2)
- tool-esptool 1.413.0 (4.13)
- tool-esptoolpy 1.30000.201119 (3.0.0)
- tool-mklittlefs 1.203.210628 (2.3)
- tool-mkspiffs 1.200.0 (2.0)
- toolchain-xtensa 2.100300.210717 (10.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 36 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Retrieving maximum program size .pio/build/nodemcuv2/firmware.elf
Checking size .pio/build/nodemcuv2/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM: [=== ] 34.3% (used 28088 bytes from 81920 bytes)
Flash: [=== ] 25.7% (used 268009 bytes from 1044464 bytes)
Configuring upload protocol...
AVAILABLE: espota, esptool
CURRENT: upload_protocol = esptool
Looking for upload port...
Warning! Please install `99-platformio-udev.rules`.
More details: https://docs.platformio.org/page/faq.html#platformio-udev-rules
Auto-detected: /dev/ttyUSB0
Uploading .pio/build/nodemcuv2/firmware.bin
esptool.py v3.0
Serial port /dev/ttyUSB0
Connecting....
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: e0:98:06:85:d6:23
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Compressed 272160 bytes to 199933...
Writing at 0x00000000... (7 %)
Writing at 0x00004000... (15 %)
Writing at 0x00008000... (23 %)
Writing at 0x0000c000... (30 %)
Writing at 0x00010000... (38 %)
Writing at 0x00014000... (46 %)
Writing at 0x00018000... (53 %)
Writing at 0x0001c000... (61 %)
Writing at 0x00020000... (69 %)
Writing at 0x00024000... (76 %)
Writing at 0x00028000... (84 %)
Writing at 0x0002c000... (92 %)
Writing at 0x00030000... (100 %)
Wrote 272160 bytes (199933 compressed) at 0x00000000 in 18.0 seconds (effective 121.2 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
========================= [SUCCESS] Took 27.74 seconds =========================
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] sh
+ python test/acceptance.py -v
testHelloWorld (__main__.AcceptanceTests) ... ok
testLEDIsTurnedOn (__main__.AcceptanceTests) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.743s
OK
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
[Gitea] Notifying branch build status: SUCCESS This commit looks good
[Gitea] Notified
Finished: SUCCESS
When I discovered the existence of Mutation Testing, it was a revelation. It showed me an almost effortless way to „test my tests“ with an absolutely clever way. Recently I did a lot of coding in Python and when I looked for a mutation testing framework for that language I found many results, but no comparison. So I did my own 🙂
The following table is a very subjective and superficial evaluation, without investing extensive digging into each tool’s configuration options. I just took the most straightfoward way to run it on a current company project.
create config file, init, baseline, execute, generate html
HTML file
199 non-killed mutants
No summary/expand for each file, only long list of findings
From the above, the maintained mutmut and cosmic ray found the most surviving mutants in my example project, while both other tools found much less. MutPy left a dubious feeling if it actually worked as intended. Both MutPy and mutatest are not maintained anymore. In the future I will look closer into mutmut and cosmic ray and see if I can tweak them towards actually using the results, as the initial ones are still a bit „too raw“ to directly act upon (which lies in the nature of my test project, too).
Visual Studio Code is my current IDE and I really enjoy it. Its quite simple and elegant in its vanilla version and the extension ecosystem is really great. In this short blogpost I want to promote a little extension which really helps my daily workflow a lot: Save & Run
What it does? Whenever a (code) file is saved, it launches a defined command.
What can it be used for? In my use case, I am using it to execute the unit tests for my project(s) whenever I save a code file. Of course I could execute unit tests and Static Code Analysis also via the GUI in VS Code or by a dedicated keyboard shortcut. But I am saving files quite often via Ctrl+S, so why not combine it together.
Lets walk through an example. After installing the extension, this is how the settings.json (either for the user or the workspace) needs to be adapted:
As you can see, you can define multiple commands, each triggered when the „match“ clause applies. In this case, I am working on a Python project, so I want to execute the command whenever I change (and save) a .py file. The „cmd“ is then the shell command to execute, here its a Powershell script executing local tests:
Thats it. Whenever I hit Ctrl+S in the Terminal view the following output is given:
As you can see, the tests are really fast, so its not executing forever. Of course thats because the project and its test cases are simple. For bigger projects this may get more beefy. However, in my current project it helps me a lot to not accidentally miss when I break the code.
I am following the Thoughtworks Technology Radar since approximately five years. Since I first became aware about it (IIRC a colleague forwarded a version to me), I enjoyed its volumes as a condensed summary of current trends in the software industry. There is some bias towards cloud technology and machine learning which is out of my current professional interest, however there are enough other concepts/tools/aspects every time which make me investigate and followup to my best possibilities either at work or in my private projects. In this blog post I will try to list those parts which are roughly new and relevent from an automotive software perspective.
As usual, there are some selected topics on the top level they are presenting. Already the first one – Platform Teams Drive Speed To Market – is highly relevant. Its no secret that the automotive industry is widely moving towards in-house platforms, recent example from the news are vw.os (Volkswagen) and MBOS (Mercedes-Benz). The particular recommendation is to handle such platform activities explicitly as products, with all the implications. Only a platform with its own set of features, product vision, dedicated team and lifecycle can actually serve its purpose. A platform handled as a side-gig of an enduser product will neither serve the platform users well nor will it provide sustainable value, plus there would be a high chance of not surviving the first generation.
The second highlighted aspect – Consolidated Convenience over Best in Class – is also very relevant in the automotive SW sector. All of us love the regular discussion which code review tool is the best, and which CI tool is the newest hot shot we must adopt. Integrated workflow tools which integrate requirements/tickets to code, integration, tools and deployment such as gitlab and github (with github actions) promise so much more convenience in inter-tool integration and generally less setup hassle compared to having multiple tools each with its own authentication foo. However, there is also much higher risk of vendor lock-in. This can result in financial dead-ends and of course sooner or later lead to loss of flexibility on adopting new technology in the workflow area.
Jumping over the third highlighted topic and going straight ahead to the fourth – Discerning the Context for Architectural Coupling – this covers the architectural state of the union address. Baseline of the message here is „it depends“. Discussions with buzzwords like „monoliths“ and „microservices“ are seldom helpful, the real discussion needs to happen on the detailled requirements, constraints and principles.
Going deeper into the four sectors of the radar – Techniques, Tools, Platforms, Languages&Frameworks – lets start with Techniques. The new entry API expand and contract is directly an „adopt“ recommendation. It means rather than breaking changes with new API versions too often its more about adding new interfaces while keeping but deprecating old ones, giving consumers a chance to adopt without breaking their usage. I can see immediate benefit of such patterns especially on highly used interfaces such as the vehicle communication interfaces. If „deprecation would be a thing (at scale)“ it could relief many breaking integration challenges in the automotive domain.
Rather than working toward a standard backlog, the team takes ownership of a measurable technical outcome and collectively establishes a set of hypotheses about the problem. They then conduct iterative, time-boxed experiments to verify or disprove each hypothesis in order of priority. The resulting workflow is optimized for reducing uncertainty rather than following a plan toward a predictable outcome.
Sounds quite interesting. I am, too, used to handling technical stories like user stories. The given approach may indeed yield more efficient and effective collaboration on technical improvements.
Another very relatable technique is Lightweight approach to RFCs. Its something which my personal workstyle already incorporated since long time. I strive to create very fast first drafts for any task and then seeking review comments from my peers. Hence, I can absolutely understand the point of this topic and clearly recommend this. Any organization in need of collaboration and frequent alignment on concepts can benefit from a very lean set of templates encouraging early feedback rather than a culture which expects „perfect first shots“.
Team cognitive load and its linked Inverse Conway Maneuver give also very interesting food for thought. I am well aware about some teams in my environment who struggle a lot with the complexity at hand, and the organzational environment adds to that complexity significantly. The Inverse Conway Maneuver – organizing the team organization around how the proper architecture of a system looks like rather than structuring the architecture around the semi-random existing organizational structure – makes a lot of sense. It chimes well with the notion of Feature Teams and team ownership.
Remote mob programming as an extension of local mob programming, which is by itself the advancement of pair programming is certainly a recommended practice in many situations. Recently I have tried to bring teams together in „remote debug sessions“ for hard inter-team nuts (=bugs) with not so great success. There is a lot of hesitance of teams to just spend some focus time together, and time difference in international setups is only one external factor. While (local) pair programming is something I think every team manager should at least try with his team a few times to see if it works out, mob programming depends an order of magnitude more mature team dynamics.
Under „hold“ we can find Peer review equals pull request. The baseline here is that pull requests are only one way to conduct peer review, and as such its a very valid claim and call for further action. Looking only at fragmented changes (which 99% of PRs usually are) and reviewing that alone doesnt give context how a codebase looks overall. Full code reviews or audits certainly give great insights for potential refactoring efforts and even re-designs, besides other learnings.
The scaled agile framework SAFe is a hold and a look in the history shows it was since the beginning. Thats interesting. I am no expert in SAFe, and only know it in theory.
Separate code and pipeline ownership is really triggering me while I write this. Thats because I recently gave a presentation about the exact same thing and why both shouldnt be separated. I am wondering if that thought appeared to me myself or if I read about it the radar earlier. Nevertheless, I think anything else than keeping both together cannot really be justified from a professional sw development context (neglecting some extreme theoretical circumstances). Having that said, its not yet an established truths, and organizational structures and other opinions enforce a separation more often than I would wish for.
The tools sector doesnt offer really relevant insights this time, its all about backend intra, web dev, ML and that kindof stuff.
In the platforms sector the first thing which appears intersting is Backstage. Its a platform developed by Spotify which in essence is a simple overview on projects going on within an organization (company). It can be used to advertise cool efforts in the developer community. Something which could be worth a try. However, as a separate tool it may lack synchronizity with the actual developer activities. Integrating such a frontend into gitlab could yield some interesting dynamics, because that could leverage existing information about activitiy of projects and use data directly from the individiual projects/repos in there.
In the languages & frameworks sector again nothing relatable.
In total the new tech radar volume give me some very valuable insights and food for more thought.
Recently I studied the Thoughtworks Technology Radar in its newest version and came upon the recommended activity of „Dependency Drift Fitness Function„, referring to the output of dependency analysis and update tools like Dependabot and Snyk. I knew Dependabot already since a while as handy tool to analyze a repo’s dependencies and create automatic pull request. The article motivated me to look into the topic for my own project. As usual I didnt want to use a SaaS solution for my personal pet projects, but use my own infrastructure. Thats where I found renovate, a handy tool for the same purpose with quite straightforward setup.
Nevertheless it took me a bit trial-and-erroring to make it work for my setup with self-hosted gitea. The renovate documentation – at least the parts I studied – didnt really describe clearly how to connect to a self-hosted tool until I found the –endpoint argument in the CLI help. In total its usage boiled down to two lines to get it running:
That resulted in a first pull request to setup a configuration file. I really liked the well written comment on this PR from the bot, giving details and even an outlook on next steps.
After merging the inital PR, the first „real“ one was created upon executing the same script as above. As announced the PR was about lifting the version of the phpdotenv package from an old version 2.4+ to 5+. Really cool, I didnt earlier check this, it must be really old.
There was a little snag as this PR couldnt be merged by itself, as the version bump resulted in some necessary code adaptions. But that was due to syntax changes from the package itself, so nothing to blame on renovate.
As a little further step I added a renovate_runner repo with only the following Jenkinsfile to execute the script every midnight. I am looking forward to future PRs from this bot.
pipeline {
agent any
triggers {
cron('@midnight')
}
stages {
stage("Run renovate") {
steps {
sh "npm install renovate"
sh "GITHUB_COM_TOKEN=ghp_FOOBAR node .\node_modules\renovate\dist\renovate.js --platform gitea --token abcdef1234567890 --endpoint https://example.com/gitea/api/v1 --labels renovate project/git_repo"
}
}
}
post {
always {
echo "One way or another, I have finished"
deleteDir()
}
}
}
In my series of book reviews on classics its time to take on another evergreen: Refactoring from Martin Fowler. It was already referenced a multiple times in the earlier books and now it was time.
The concept of refacoring is well-established nowadays, and I would say that only a company culture which incentivizes and motivates a spirit of constant refactoring can be a state-of-the-art software company. In my experience, this is easier said than done, especially when immense release pressure and tight resources lead to the well-known vicious circle of crunching and task forces, which never end. When one release was successfully squeezed out „somehow“ and the next one is already knocking on the doors, how could anyone expect refactoring of code which actually „works“ (it made it into the last release after all!!!). I had such discussions many times, and until today I dont feel strong enough in my arguments to convince senior managers in typical situations. Arguing with sustainable code and continuous improvement when the other side virtually puts the existence of the whole organization at risk is an uphill battle. But its worth fighting.
In essence when you refactor you are improving the design of the code after it has been written. […] With refactoring you can take a bad design, chaos even, and rework it into well-designed code. Each step is simple, even simplistic. You move a field from one class to another, pull some code out of a method to make into its own method, and push some code up or down a hierarchy. Yet the cumulative effect of these small changes can radically improve the design. It is the exact reverse of the normal notion of software decay.
Martin Fowler: Refactoring, page 9
I like this simple but strong definition, which underlines the cumulative effect of small improvements. Refactoring is not the same as the „grand redesign“ or „from scratch“ approaches which are often taken and loved, especially when the staff (developers, managers) is changed. Refactoring can achieve the same goals with either the same or a different staff.
After some introductory words Fowler goes ahead with a concrete example. He emphasizes the repetitive nature of changing something a little bit, then running an extensive unit test suite and then committing the changes to a repository. This approach is something I immediately started to exercise. For that I had to setup some of my testing scripts to work locally (they were purely in my CI before), but the effort was totally worth it. With my unit test coverage being 98% nowadays plus mutation testing in place, I dont have to worry a lot to break existing functionality while refactoring, and as a lost resort I have all the extensive API and acceptance testing in my CI before I can merge on master.
The true test for good code is how easy it can be changed.
Martin Fowler: Refactoring, page 77 (translated back to English)
This quote may be controversial among people with low exposure to professional software development (juniors, managers who gave up on programming a while ago), but for professionals in my environment its fortunately common sense. Which doesnt mean that its applied constistently, though.
If someone tells you that their code during refactoring didnt work for a few days you can be quite sure that they didnt apply refactoring as such.
Martin Fowler: Refactoring, page 79 (translated back to English)
Such reworks or redesign of larger parts of a codebase of course can and may happen, too, but I appreciate Fowler’s take on a clear separation. Especially in arguments with upper management, clear terminology can help. If refactoring can mean everything happening to a codebase except adding new functionality, its probably to vague. Restricting its meaning to pure tiny and small changes reduces risks a lot and may help to increase acceptance.
Refactoring Helps You Program Faster
Martin Fowler, Refactoring, page 82
This was and is also my personal strong belief, and I think there is enough evidence that this is a fact. I mentioned earlier sustainability, and if a well-design and maintained code base based on clean code principles and with constant refactoring exists, its the best way to get a graph like the red one below.
Note also the short part in the lower left corner, where poor design for a while may have more functionality than good design. Naturally, with hacky proof of concepts hitting production, you can push out some functionality „fast“, but at the cost of low efficiency later on. On pages 90f (German version) declares this the major argument in favor of refactoring – in the end its about the business value it provides, and that one can be immense.
On pages 86f Fowler discusses if one should reserve dedicated time for refactoring. I heard before of models like „every third sprint is a refactoring sprint“ and of course situations where „management decided the next sprint is for refactoring to fully focus on features after that again“. Fowler argues that reserved refactoring slots shouldnt be a thing. I tend to agree with him, by stating that refactoring should be considered in day-to-day work efforts and not be a sepereate activity in contrast to „normal development work“ (which it should be a part of). However, as the value of refactorings is hard to measure and aforementioned „implicit“ integration into the developer workflow easily gets it ignored or watered down, some accompanying activities and events encouraging refactoring may provide benefit.
What Do I Tell My Manager? […]
Of course, many people say they are driven by quality but are more driven by schedule. In these cases I give my more controversial advice: Don’t tell!
Martin Fowler: Refactoring, page 89
Similar to Bob Martin, Fowler argues with refactoring being an essential part of professional software development, thus its not something a manager should even have the change to interfere with in particular. I agree – mature develops should just do it and get the time needed. Asking management for approval for such a core activitiy in the actual coding, that it would be weird to explicitly ask for it. Do you ask for permission to apply certain design patterns, too? Of course, when asked, management will respond and the answer in probably 80% not what you would do. Management will probably realize at some point of refactoring gets too extensive (if that is even possible), but in the end refactoring is for everyone’s best (see the graph above).
On pages 93f Fowler has some interesting input on the interaction of (feature) branches and refactorings which I didnt see before. In essence its simple: Both are working in opposite directions. If there are multiple branches living in parallel and in one of them refactoring is happening, it may make the other branches unmergable (and vice-versa). So if a company or project wants to encourage refactoring, it should avoid active branches wherever possible.
There are more arguments for refactoring and a myriad of hints and tricks how to apply it, when to apply it and how to carry it out in an optimal way. The second part of the book contains detailled descriptions of all types of refactorings. I have skipped through them, but I know I wouldnt remember them exactly anyways. However, with the gist of things in mind I did a major refactoring of my current pet project, and it was really fun and productive. Now my codebase is even cleaner (I did already a few rounds after reading Clean Code) and I am looking forward repeat it while I continue adding functionality.
It was a very good book and a certain recommend, if you didnt read it already.
My third book review leads me to the third book in a row from Robert C. Martin. You may think I am a fanboy or such, but its just that as written before all those books and more I got provided from my employer, and somehow I liked the flow of Martin’s books, so I read them one after the other. But fear not, the next book will be from another author.
So „Clean Agile“: Already the title is a bit triggering. I remember a online talk from another big agile representative (forget the name though) who was very clearly discouraging to use „Agile“ as a noun for anything, but always use it in its grammatically correct way as an adjective. Thats what I thought about when holding the book in my hand. However, I dont really care for the difference. Grammar is the one side, the other is that agile/agility has been a standing term in the sw industry for a while now, so one may as well use it in its noun form. Nevermind.
The first thing noteworthy is the history lesson about the waterfall process. According to Martin, the 1970 paper from Winston Royce which according to popular belief introduced waterfall and initiated its establishment, was actually arguing against this model. If that is true, a lot of people must have read only the first part of that paper and then went on implementing that part – because it chimed well with the notions of Scientific Management which were cool back then.
Martin is also very explicit about the small vs big team problem. In his opinion the organization of big teams is a solved problem since ages. What was open was the organization of small teams developing software. To scale from there seems to be the easy part for him. In theory, he may be right, but looking at my project which we scaled from 1 to 65 teams in years working on one product, the organism and interactions between teams and the overall organization cannot be reduced that easily. That would assume an organizational maturity on project level and on each single team that is hard to reach under realistic circumstances. I am not saying its not feasible, just that its not that simple. But Martin has shown to simplify a lot to make his points.
Another new information for me was the term „Iron Cross“ of project management:
I agree with this assessment (of course in a theoretic setup you may be able to achieve all four, but in this world this is unlikely). On the following pages, Martin describes quite typical project development in the waterfall world up until the deatch march phase.
Another example of Martin’s provocations is his statements that „The loss of hope is a major goal of Agile.“ Sounds crazy, right? What he aims at by using the metrics (like velocity) in a proper way, arbitrary and unrealistic deadlines, which are based on hopes, can be overcome, and gradually more realistic estimates can come out. This is also were he makes this statement:
Some pages later, Martin once again talks about the „grand redesign“ – the typical „cure“ when a software organisation has maneuvered itself into a dead end. He repeats his example from his earlier books, where a Tiger Team to redesign the software from scratch is caugth between having to catch up with the legacy software’s development while generating no income.
On page 50 Martin makes a memorable insight:
I really liked this statement. Software is in many cases connotated with the stance that old software/code bases are mostly getting worse, seldom better. Of course we all know good cases, mostly Open Source projects going on since decades (Linux Kernel, gcc, Latex, …), but in the proprietary software industry its often different. I cannot count how often old software really got worse over time – no matter if it was disimproved by rushed hacks or not changed at all. Martin is stating the obvious, but in that clarity I never saw that.
I like this statement, because in few sentences Martin clarifies a typical debate around agile approaches. Some nitpickers – often with the aim to show the unsuitability of agile practices for tough industry business – claim that agile is anti plans or discourages plans.
This is a very important observation and I can confirm it from practice. As a project manager I am monitoring team’s velocity charts. However, I am not doing this to enforce rising velocity charts, but to identify trends in underachievements (commitment vs achievements). Speaking about the average mature team, I think the best to aim for is a stable sprint velocity over longer duration of times, leading to a sustainable pace (Martin writes more on that on page 103). The last paragraph above gives a nice approach how to determine velocity inflation.
I really like this quote, it fits to my „natural instincts“. My work and contributions are most sustainable when I can work more or less normal hours. That means I can rest, stay sane and – more relevant to my employer – reflect on what is going on at work. As a team and project manager my day at work is usually crammed with meetings, mails and nagging people. Very rarely I have time to think deeply about what goes on. Hence, the best ideas I have after work, during weekends and vacations. I usually make sure to take notes about those ideas, so I can followup during working times.
On pages 106f Martin shares another interesting anecdote about the „developer hierarchy“ at a printer company. The baseline is, that the printer software developers enjoyed a lot of praise for their direct contributions to the company’s main business. However, that led to elitism and closing up of their code. Other software developer working in indirect matters did receive lower trust and couldnt contribute that much. I think this is something to observe also around my work. The ones directly developing fancy features which make it on magazines and news pages feel much more appreciated than the ones working in the machine room, keeping the whole machinery running. I think the boundaries between those groups must be as permissive as possible.
Simple but powerful words. I cannot believe how often CI is preached and promised, while it boils down to integration of a full team’s work once a sprint or even less.
Not much more to say, except that this is hard in traditional environments to achieve, and very easy to loose once gained. Just recently I got the chance to focus more, which I really appreciate.
I love that quote because it is just true. And I admit that sometimes I am one of those naive folks. However I work to be it less every day.
Towards the end of the book, Martin lays out the new movement of Software Craftmanship. I frankly didnt hear about it before, but I like the ideas about it.
Many will recall the format following the Agile Manifesto. However I see it a very valuable extension from the perspective of developers.
Let me end here. Probably long enough text for anyone ever to read here 🙂