This is a bit of a long post describe the custom toolchain I created—and philosophy behind it—for the books that I’ve self-published. It was created to solve a problem that plagues technical books: keep them updated as libraries and programming environments change. My current solution completely automates all code samples, command-line invocations, screenshots, and many of the diagrams.
Jason Swett asked on Twitter if anyone has an app with 2000+ tests that does not have a severe flaky test problem. I have two such apps, and I want to share the lengths I’ve gone to to make the tests not flaky.
In a nutshell, you have to build and design for testability at all levels, plus ensure your tests are clear about what is the cause of a failure.
Avdi Grimm has created great videos about Ruby called Ruby Tapas, and put up an older one as a video for his new Graceful Dev offering. The video is called Barewords in Ruby and it’s a good demonstration how to make changes to a system without changing a core piece of logic.
While I like how Avdi approaches it, I’d make different trade-offs and make changes that result in code with behavior more easy to predict than code that requires fewer changes. Let’s see.
One of the lowest-effort, highest-value practices you can adopt for sustainable development is to keep your dependencies updates frequently. The foundation for doing so is to have a clear and reasonable versioning policy. This post includes one for Rails apps that I have used for years and has served me and my co-workers well. It has some implications for specific procedures you will adopt as well.
In a nutshell: stay no more than 2 minor versions behind Ruby and Rails, and keep everything else as up to date as you can as often as you can.
RSpec’s internal DSL allows creating some difficult-to-sustain structures and code, but there is one guiding principle that has helped me avoid making tests that are too weird:
RSpec tests should be examples of how the code under test would be used.
Let’s see a few examples: using
subject and avoiding predicate matchers.
The app I work on has a lot of API integrations. These API calls are often tied into various business processes. By wrapping an adapter around each API, presenting only the features of that API my app needs, I can more easily manage and test my app. It also provides clear documentation about how my app uses each API. I’ve heard this called a service wrapper and it’s incredibly useful.
If I had one piece of advice for using Rails, it is to treat Rails for what it is, not what you might like it to be. This was the subject of my talk at Rails Conf 2022, but it has a few practical implications for, among other things, how to organize the code in your Rails app.
Test-Driven Development is often sold as a way to be more productive or produce better designs, but it’s these unprovable claims that make skeptics even less likely to adopt the practice. Instead, TDD should be sold as a tool to reduce the risk of software not working as intended without expending huge amounts of effort and time doing so. Because that’s what it does (yes, it’s about testing).
Product design—really all of design—is about how the user’s problem is solved. It’s about how it works, not how it looks. Each problem gets addressed in one of five ways: direct support via the happy path, a supported edge case, the customer support team, the engineering team, or oblivion (where it is not actually solved).
Inexperienced designers focus entirely on the happy path, whereas most product designers focus only additionally on edge cases. Even then, the lack of involvement from engineering and customer support can leave the design woefully under-developed.
This post outlines a slightly structured model for thinking through a product design inclusive of the entire user experience, including customer support.