during: 2024
Exploring Generative AI
Generative AI and particularly LLMs (Large Language Models) have exploded into the public consciousness. Like many software developers Birgitta is intrigued by the possibilities, but unsure what exactly it will mean for our profession in the long run. She has taken on a role in Thoughtworks to coordinate our work on how this technology will affect software delivery practices. On this page she posts a series of memos to describe what she and our colleagues are learning and thinking.
Instead of restricting AI and algorithms, make them explainable
The steady increase in deployment of AI tools has led a lot of people concerned about how software makes decisions that affect our lives. In one example, its about "algorithmic" feeds in social media that promote posts that drive engagement. A more serious impact can come from business decisions, such as how much premium to charge in car insurance. This can extend to affecting legal decisions, such as suggesting sentencing guidelines to judges.
Test-Driving HTML Templates
When building a server-side rendered web application, it is valuable to test the HTML that's generated through templates. While these can be tested through end-to-end tests running in the browser, such tests are slow and more work to maintain than unit tests. Unit tests, written in the server-side environment, can check for valid HTML, and extract elements with CSS selectors to test the details of generated HTML. These test cases can be defined in a simple data structure to make them easier to understand and enhance. Browser testing tools are still needed to unit test the behavior of the generated HTML, together with the associated CSS and JavaScript.
Data Fetching Patterns in Single-Page Applications
When a single-page application needs to fetch data from a remote source, it needs to do so while remaining responsive and providing feedback to the user during an often slow query. Five patterns help with this. Asynchronous State Handler wraps these queries with meta-queries for the state of the query. Parallel Data Fetching minimizes wait time. Fallback Markup specifies fallback displays in markup. Code Splitting loads only code that's needed. Prefetching gathers data before it may needed to reduce latency when it is.
Using Footnotes
How I use, and render, footnotes
Uncovering the Seams in Mainframes for Incremental Modernisation
Mainframe systems continue to run much of the world's computing workload, but it's often difficult to add new features to support growing business needs. Furthermore the architectural challenges that make them slow to enhance also make them hard to replace. To reduce the risk involved, we should use an incremental approach to legacy displacement, gradually replacing legacy capabilities with implementations in modern technology. This strategy requires us to introduce seams into the mainframe system: points in which we could divert logic flow into newer services. In a recent project we investigated several approaches to introduce these seams into a long-lived mainframe system.
Farewell John Kordyback
John Kordyback, a treasured colleague and friend, died last week, aged 64.
Measuring Developer Productivity via Humans
Measuring developer productivity is a difficult challenge. Conventional metrics focused on development cycle time and throughput are limited, and there aren't obvious answers for where else to turn. Qualitative metrics offer a powerful way to measure and understand developer productivity using data derived from developers themselves. Organizations should prioritize measuring developer productivity using data from humans, rather than data from systems.
What if we rotate pairs every day?
Benefits of pair programming are widely accepted but advice around pair rotation remains controversial. When and how frequently should teammates rotate pairs? And… What if we rotate pairs every day? We worked with three teams through an exercise of daily pair rotation. We developed a lightweight methodology to help teams reflect on the benefits and challenges of pairing and how to solve them. Initial fears were overcome and teams discovered the benefits of frequently rotating pairs. We learned that pair swapping frequently greatly enhances the benefits of pairing. Here we share the methodology we developed, our observations, and some common fears and insight shared by the participating team members.
Patterns of Legacy Displacement
When faced with the need to replace existing software systems, organizations often fall into a cycle of half-completed technology replacements. Our experiences have taught us a series of patterns that allow us to break this cycle, relying on: a deliberate recognition of the desired outcomes of displacing the legacy software, breaking this displacement in parts, incrementally delivering these parts, and changing the culture of the organization to recognize that change is the unvarying reality.
Periodic Face-to-Face
Improvements in communications technology have led an increasing number of teams that work in a Remote-First style, a trend that was boosted by the forced isolation of Covid-19 pandemic. But a team that operates remotely still benefits from face-to-face gatherings, and should do them every few months.
Engineering Practices for LLM Application Development
LLM engineering involves much more than just prompt design or prompt engineering. In this article, we share a set of engineering practices that helped us deliver a prototype LLM application rapidly and reliably in a recent project. We'll share techniques for automated testing and adversarial testing of LLM applications, refactoring, as well as considerations for architecting LLM applications and responsible AI.
Continuous Integration
Continuous Integration is a software development practice where each member of a team merges their changes into a codebase together with their colleagues changes at least daily. Each of these integrations is verified by an automated build (including test) to detect integration errors as quickly as possible. Teams find that this approach reduces the risk of delivery delays, reduces the effort of integration, and enables practices that foster a healthy codebase for rapid enhancement with new features.
Legacy Seam
When working with a legacy system it is valuable to identify and create seams: places where we can alter the behavior of the system without editing source code. Once we've found a seam, we can use it to break dependencies to simplify testing, insert probes to gain observability, and redirect program flow to new modules as part of legacy displacement.
My favorite musical discoveries of 2023
Six of my favorite new music acquisitions in 2024