The Church of Best Practices

The Church of Best Practices

Stefan Hodges-Kluck

by Stefan Hodges-Kluck on June 3, 2025

Lately I've been thinking a lot about best practices in software engineering. Not specific practices, but the general concept of best practices. Broadly speaking, “best practices” are the authoritative series of design patterns, architectural decisions, and stylistic choices that engineers should adhere to when they are designing and developing apps. These practices can come from a variety of sources—general programming principles like S.O.L.I.D.; widely-read books like the Gang of Four’s Design Patterns, Eric Evans’ Domain Driven Design, or Robert Martin’s Clean Code and Clean Architecture; tech companies like Google; SDK and API developers; technical consultancies with app-building experience; individuals within a company with domain/technical knowledge; and many more. From how team leads design an application’s underlying structure to how individual engineers decide how to name variables, best practices help create a uniformity throughout a codebase that makes expanding and improving the software faster and easier in the future.


What is particularly difficult about software engineering is the diversity of opinions and arguments that get lumped under the umbrella of “best practices.” Unlike other forms of engineering, there is no official governing board that evaluates and credentials a certain way of doing things. And as an old XKCD comic jokes, establishing a new standard usually results in just another thing for devs to argue over. Anyone with an internet connection (including some guy with a personal blog site) can post about what they think is the best way to design an application’s codebase. As a result, there are multitudes of opinions on just about every decision one can make when it comes to software, whether it’s what qualifications are necessary to enter the field, which language patterns to adopt, what frameworks/libraries to use, what stylistic and structural patterns to follow in a codebase, or any other fork in the road of app development that requires a decision. When making such decisions, some sources certainly tend to hold more authority than others—I’ll take Flutter advice from the Google team that wrote the language before taking it from a random post on StackOverflow or a vague response from ChatGPT. Yet the sheer volume of information on the internet makes it necessary for engineers—particularly newer ones—to know how to determine what counts as a reliable source of authority. Making things even more difficult is the fact that so many of our sources of authority are also companies like Google, Apple, Meta, and Amazon, who have vested financial interests in people staying within their ecosystem, and accordingly preach practices that tend to align with their products. 

Moreover, while engineers can sift through mountains of sources on the internet to determine best practices, often “best practices” in a particular codebase are simply the result of the initial stages of a product’s development that have ossified into rigid rules. I have certainly encountered times in my career when I cloned a repository for the first time, asked about a given pattern I see in the code, and received a response somewhat along the lines of “I don’t know, that’s just how it’s always been.” And let’s be honest, I’ve also been the one giving that same response to others onboarding. Just as laws become more difficult to remove once they’re in the books, it’s much easier for code patterns to solidify once they have been checked into a repository. The initial decisions made in designing a system are essential, as they will influence what others do in the future. 

The diversity of choices available to software engineers today reminds me a little of the first few centuries of Christianity. From the earliest Gospels to the theological tracts of the so-called Church Fathers of the fourth century C.E., early Christian texts abound with evidence of the variety of questions that the earliest followers of Jesus faced—questions over the relationship between Jews and Gentiles, the role of women, the relationships between clergy, monks, and the lay population, interaction between various linguistic communities, and different theological interpretations of the Trinity and Christ’s nature. Not only did Christians of the first few centuries—like Christians today—have several different interpretations of the Bible, but in those early years there was not even consensus on what went in the Bible. What modern readers call the Bible is a collection of several books—the word Bible comes from the Greek biblia, or books—separated into the Old Testament (books written before the life of Jesus Christ, about the people of Israel) and the New Testament (books written about Jesus and his immediate followers). Even within the Old and New Testaments, there are texts containing different topics, written by different authors in different communities, over the span of several centuries. They weren't the only books that early Christians read and wrote either, as there are multiple Gospels attributed to apostles that didn't make it into the canon. There's even a Gospel in which an adolescent Jesus uses his divine powers to terrorize schoolteachers in an early Christian version of the Twilight Zone’s “It's a Good Life.” The authority of the books that made it into what we call the New Testament was not a pre-ordained outcome, but rather the result of centuries of communication and disputation among different communities throughout the Mediterranean.

Now software engineers are dealing with similar disputes over authority, but with communities that have developed over a few decades and not several centuries. As with the field of software engineering, in early Christianity there was no formal body of Christians determining which books were canonical—or, rather, there were multiple competing bodies. To be sure, software engineers are, on the whole, much less concerned with monopolizing authority and solidifying communities than were proto-orthodox bishops who decried their rivals as heretics. Engineers at Google even emphasize that their practices are not meant to be taken as dogma. Yet the diversity of approaches among present-day software engineers has certainly resulted in certain dogmatic attitudes. From drawing boundaries around what counts as “real” software development (which usually means looking down at front-end web developers) to reddit threads proscribing certain frameworks as superior, engineers certainly do their fair share of preaching. The rise of AI, meanwhile, gives even more fuel to the fire of software dogmatism. 

A few years ago, I was a lot more rigid in my adherence to what I considered best practices. In 2023, when I joined Very Good Ventures (VGV) and was first exposed to Very Good Architecture, I felt I had found the gospel of application development. Practices like layered architecture, state management through bloc, and 100% test coverage through unit tests all helped create code bases that I found approachable, maintainable, and scalable. Empowered with this newfound approach, I felt ready to spread the good word to the world. With the zeal of the converted, I set out to banish heretical practices from the codebases I worked on, and to bring the masses of Flutterdom into the Light.

Now, two years later, I take a different approach. While I no longer work at VGV, I still think that their practices offer the best framework for building a Flutter app—and, indeed, most of the lessons I’ve learned through Very Good Architecture can be applied to development in any language. However, in retrospect, I was probably a little too eager to preach the good word without understanding how it would solve specific technical and product problems. I worked on a large project within a complex organization, and unwittingly let myself believe that all the project’s problems could be solved with more repositories and unit tests, without always stopping to think about how the app I was building interacted with other crucial APIs and services that constrained the architecture. While I certainly made valuable contributions, I also think I went down a few rabbit holes refactoring things so they were done “right” just for rightness’ sake and writing unit tests to hit code coverage, without thinking about how tests should simulate actual user interactions.

Ironically, this sort of attitude limits the effectiveness of an architectural paradigm, because it leads to people thinking they have to follow patterns blindly because that's what you're “supposed” to do. Dogmatic rigidity can make even the soundest of practices transform a healthy engineering department into a battleground of factions. This factionalism distracts from the real business problems that an application seeks to solve, as engineers waste time brow-beating rival approaches and arguing over opinions in PRs. A software team divided into factions looks a little like the landscape of early Christianity, with diverse communities all considering themselves orthodox and outsiders heretical.

I’m not arguing that setting best practices is a bad idea because it leads to factionalism. Indeed, any project with more than one person working on it needs some level of standardization to make it easier to reason through code in such a way that maintenance and improvements are possible without causing regressions. But it’s also very easy to get so caught up in arguments over which practices/paradigms to follow that we lose track of the actual problems we’re trying to solve. As software engineers, we need to find that tricky balance between rigidity and flexibility, ensuring unity without clinging dogmatically. Here, it helps to remember what you’re trying to accomplish with your practices. For instance, why are you trying to hit 100% test coverage? If it’s just to make the pipeline happy and get a green checkmark on your PR, you’re doing it wrong. Ideally, you should be hitting coverage to ensure that the code you write is adequately protected against unexpected bugs and regressions in your actual product. When you approach testing this way, then you can consider whether the tests you write are actually simulating situations that end users will encounter, or if they are just setting up just enough of your code to hit coverage for coverage’s sake.

Best practices are all the more important now that AI code generation tools are so prevalent. In addition to understanding and adhering to AI design patterns, we also need to remember why we advocate for the practices we adopt. I don’t think it’s a secret that I am a bit critical of how AI has bolstered movements like low/no code software and vibe coding. But I don’t want to give a dogmatic “thou shalt not use AI to code” commandment, to be followed blindly for the sake of preserving “legitimate” programming identity. My critique of certain elements of the AI movement have to do with the complex nature of app development. Software engineering is a field where you can make something basic very quickly, so you think that you know what you’re doing, only to realize as you progress just how much more there is to it. It is very easy to get twisted in webs of convoluted conditionals, edge cases, and interdependent moving parts. Seven years later, and I still have to be careful of these traps. The faster you produce code and the less you understand it, the more likely this is to happen. If you can set up LLMs with the right contexts to integrate with your project, and you have enough technical knowledge to understand AI-generated code and how it interacts with all of the different dependencies needed to run, host, and deploy your software, then it can provide some benefit. But if it’s a magical “make my app so I don’t have to think about all that scary programmer code” box, then you might want to think more about best practices. 

Like the earliest Christians, software teams today have a diverse body of authorities to draw from in determining what practices they should follow. In this landscape, it’s essential to have sound reasons behind the architectural patterns and practices we adopt. A well-structured and documented codebase can provide enough uniformity to make contributions easy and quick for a whole group of engineers. Allowing opinions to solidify into dogma, however, will only create disputes that draw attention from the core goals of a product. It’s up to us to strike the right balance.

Subscribe to blog posts

Enter your name and email to receive new blog posts directly in your inbox.