EN Version:
After reading some reviews of the book A Philosophy of Software Design, by John Ousterhout, I finally decided to read it. I was putting off reading this, not because I doubted I would learn anything, but because I thought it would be just another software engineering book. Moments after finishing this reading, I realize that it is another software engineering book - but with a differentiating characteristic: the way it conveys the message!
As all the reviews mention, it is a good read, which, even though it is not deeply technical, presents the challenges and identifies red flags in the software development process. Although it touches superficially on subjects covered in several other books, such as Clean Code by Robert C Martin (I think it is impossible to talk about good software quality without talking about Robert C Martin), Ousterhout presents a more philosophical view, which as a technologist I am not used to having, about the challenges and objectives of good software design. That alone made it worth reading.
The purpose of this post is not to do (another) review of this book, but to share a reflection on how software is developed with the support of generative AI and how the challenges that arise from this resemble the challenges faced decades before the emergence of these tools. Therefore, you will find an analogy, a parallelism between b.AI and a.AI (before AI -After AI).
For this comparison to make sense, it is essential to understand the context and message that John Ousterhout presents in Chapter 3: Working Code Isn't Enough. Therefore, before going into the critical opinion, I will explain and comment on what the author conveys in this chapter. If you have time, I recommend that you read the excerpt from this chapter, which made me think and led me to write this post, at the end.
Subtly and philosophically, the author presents tactical programming, which is nothing more than developing software in total anarchy, without taking into account quality metrics such as maintainability or cognitive capacity. The author ends by describing the extreme go-horse methodology. The goal is just to deliver quickly and functionally, even increasing technical debt, paraphrasing the author “even if this means increasing complexity or applying a trick or two, if this makes the current task complete quickly”. I consider this perfectly reasonable in certain cases, such as when developing TOC, an MVP, an urgent bugfix, or when we are developing something disposable. The best solution is the one that best meets the requirements.
It is emphasized that this approach is not sustainable in the long term and any software engineer with a few years of experience can confirm this empirically. “With tactical programming, it is almost impossible to produce a good system design”, this is because complexity becomes incremental. Maintaining this approach in the long term will cause technical debt to increase more and more and the system will end up becoming so complex that it will almost be necessary to get a PhD to understand it (of course it is ironic... or maybe not...). It goes even further, suggesting that the situation will end up becoming a fish with its tail in its mouth: Current deadlines will mean that future planning is not prioritized, increasing complexity and technical debt. As the system becomes more complex, all available time will be allocated to new tasks because now it takes 2x or 3x longer and it is necessary to work on top of a system complex becoming increasingly complex. This cycle repeats itself indefinitely. As the author says: “Complexity is incremental.” On the other hand, it presents, in the form of a solution or correction, strategic programming, which is nothing more than realizing that functional code is not enough. Maintaining a good system design is crucial in the long run. To achieve this, you must invest in planning and design tasks, following good software engineering practices, and not just “writing code”. When performing a task, you must have a holistic view of the entire system and not just the specific task.
“Strategic programming requires an investment mindset. Rather than taking the fastest path to finish your current project, you must invest time to improve the design of the system. These investments will slow you down a bit in the short term, but they will speed you up in the long term”
Once again, it is empirically clear that this is a very common problem in large-scale software. These problems arise from the nature of human beings and the demands and competitiveness of the business world.
The author mentions that in most organizations there are developers he calls “tactical tornadoes”, but in this post, I will just call them “tornadoes”. Tornadoes are dedicated professionals who implement new features faster than anyone else, focusing only on what needs to be done to finish them. These professionals are generally seen as heroes by the management of organizations, usually non-technical.
“They are rarely considered heroes by the engineers who must work with their code in the future. Typically, other engineers must clean up the messes left behind by the tactical tornado, which makes it appear that those engineers (who are the real heroes) are making slower progress than the tactical tornado
It's a case of saying: Real heroes don't wear capes, they refactor the big balls of mud.
When I was reading this chapter I immediately remembered the new wave (or not so new) of “vibe coding” I want to emphasize that this book was published in 2018, before generative AI tools were available, but it was based on decades of experience. It is amazing how a book written before the popularization of generative AI tools would come to reflect so many of the challenges and dilemmas that these tools pose.
brought.
With the advent of generative AI tools, a new concept called vibe coding is emerging, which consists of using and being dependent on an artificial intelligence assistant to write code. With this approach, it is a fact that non-technical people, students or professionals with little experience, can develop functional applications relatively quickly, without much effort, simply saying in natural language, the application they want. I use lovable.ai to design UIs for my projects, even though I'm aware of all the implications this may have because I don't like coding UI. Since trying this and other tools like it, I prefer to pay a few dollars to have this work done for my projects.
We are noticing that this concept is being used more and more by developers and is spreading so quickly and being promoted as a fairy tale that stakeholders in the software development world expect nothing less than generative artificial intelligence writing code and that professionals can deliver features faster making their applications evolve faster than ever before.
When I was reading the book mentioned above and the author introduced tactical programming and tornadoes, I immediately remembered this new wave of taking vibe coding to the extreme and how this approach and those who apply it, the vibe coders, are being seen as heroes. “History doesn't repeat itself, but it often rhymes.” – Mark Twain. Will the extreme vibe coders be the new tornados?
By this, I am not underestimating the many benefits of using AI tools throughout the software development process. However, relying on a tool to develop software is taking an uncalculated risk. As we know, all artificial intelligences are trained with data, so it can be concluded that the generated code is based on several examples and repositories that were used to train the model. Assuming that the quality of the code available on the internet, used to train the AI, follows a normal distribution, the code generated by the AI tools will be something around average. Also assuming that the complexity and documentation of the code of the projects follow the same distribution, the results will be the same. This reasoning can be applied to other metrics.
Another problem is context. Previously, when talking about John Ousterhout's vision of strategic programming, I highlighted in bold the sentence: “When executing a task, one must have a holistic view of the entire system and not just of the specific task”. It is practically impossible to provide the entire holistic context of the software engineering process (planning, requirements, design, architecture at various levels, etc., etc., and especially the corner cases and unusual decisions to meet a specific requirement or strategy), of a complex application, for generative AI to code a new feature or bugfix, due to its limitations. If someone with knowledge of AI read the previous sentence, they certainly thought of RAG systems as a potential solution. Copilot uses RAG to empower its context, but RAG has several limitations when working with a context directly with several connections and dependencies, as is the case with software. However, it is excellent at working in small and delimited contexts. Did this term sound familiar (DDD)?
I will wander in thought:
The tornadoes you are reading about certainly thought of a more complex solution, such as creating a chain to read all the requirements documents, several agents working together, debating among themselves, etc, etc, etc. They certainly also thought that agents also need a theoretical context (knowledge/theoretical foundations) and for
that they must have fine-tuning or even another complex RAG system composed of several agents. As usual in a tornado, he ended up creating a system that at first is capable of showing the sky in the form of results, but which is extremely complex, expensive, and difficult to maintain, which will end up proving to be hellish when the requirements and complexity of the system evolve.
So generative AI can't be used in the development process of software that follows good software engineering practices? It can and should be! As long as it is used as a productivity tool and pair programming, not order programming (I just invented the term). Let it be a pair and not your slave. Note that Copilot’s slogan is precisely: “Your AI pair programming”.
I use it on everything! From brainstorming, requirements analysis, information repository, and also code generation and review. Sometimes I find myself debating ideas with ChatGPT as if it were my coworker.
I have been using these tools and I am extremely convinced that in the long term, it will become clear that they are and will continue to enhance well-designed software, making it possible for features to be developed and value to be added more quickly. This is because the engineer will be able to allocate most of his energy and time to designing and planning the software rather than writing code. However, it is extremely important that the fundamentals are always consolidated to ensure that the AI-generated code meets the quality requirements and, if it does not, that the engineer is able to correct it. It is necessary to prevent the paradox of the abundance of information from making professionals less capable. As? Actually, I don't know... It's one of the biggest challenges in training today. Maybe it will be a topic for another post.
On the other hand, I am also convinced that these tools will make software that uses tactical or extreme go horse approaches, which are already extremely complex, have technical debt and low maintainability, even worse. An AI tool is trained with standardized code and cannot understand extreme complexity, shortcuts, tricks, or other abilities introduced by tornadoes. Therefore, it generates the code that is within its knowledge, making the solution even more confusing and complex, contributing to the increase in technical debt.
I believe that in the future, perhaps near, new real heroes will be needed to solve a lot of the confusion created by AI-dependent vibe coding. In short, there is enormous potential to enhance well-designed solutions, but also to enhance the confusion caused by tornadoes. Those who know me know that I usually make analogies and to finish, here is another one:
Having a physics encyclopedia and a scientific calculator does not make one a physicist. Someone with these two objects can easily come up with solutions to everyday problems, such as the average speed of the ball in Cristiano Ronaldo's free kicks, the energy consumed by a new household appliance or even the braking distance of a car (yes, except for Ronaldo's, these examples are authored by an AI), but they can hardly reach conclusions and solve more complex problems that require articulation and in-depth knowledge.of several fundamental concepts of physics. The same happens in engineering software.
It's the famous example of: "You won't believe the application I made! I'll send you the link so you can see it: https://localhost:3000".
PT Version:
Depois de ler algumas reviews do livro A Philosophy of Software Design, de John Ousterhout decidi, finalmente lê-lo. Andava a adiar esta leitura, não porque duvidasse que podia aprender algo, mas por achar que seria mais um livro de engenharia de software. Momentos depois de acabar esta leitura, percebo que é na verdade mais um livro de engenharia de software - mas com uma caraterística diferenciadora: a forma de passar a mensagem!
Assim como todas as reviews mencionam, é boa leitura, que mesmo não sendo profundamente técnico, apresenta os desafios e identifica red flags no processo de desenvolvimento de software. Embora toque superficialmente em assuntos abordados em diversos outros livros, tais como clean code de Robert C Martin (acho que é impossível falar de boa qualidade de software sem falar de Robert C martin), Ousterhout apresenta uma visão mais filosófica, que como tecnólogo, não estou habituado a ter, acerca dos desafios e objetivos de um bom design de software. Só por isso já valeu a leitura.
O objetivo deste post não é fazer (mais) uma review deste livro, mas sim partilhar uma reflexão sobre da forma como se desenvolve software como o apoio da AI generativa e como os desafios que com isso advêm se assemelham aos desafios enfrentados décadas antes do surgimento destas ferramentas. Portanto, encontrarão uma analogia, um paralelismo entre b.AI e a.AI (before AI -After AI).
Para que esta comparação faça sentido é essencial perceber o contexto e a mensagem que John Ousterhout apresenta no capítulo 3: Working Code Isn’t Enough. Por isso, antes de entrar na opinião crítica explico e comento o que o autor transmite neste capítulo. Se estiverem com tempo, recomendo que leiam o excerto deste capítulo, que me deixou a pensar e levou a escrever este post, presente no final.
De uma maneira subtil e filosófica, o autor apresenta a programação tática, que nada mais é que desenvolver software em total anarquia, sem ter em conta métricas de qualidade tais como capacidade de manutenção ou capacidade cognitiva. O autor acaba por descrever a metodologia extreme go horse. O objetivo é apenas entregar rápido e funcional, mesmo aumentando o débito técnico, parafraseando o autor “nem que para isso seja necessário aumentar a complexidade ou aplicar um truque ou dois, se isso fizer com que a tarefa atual seja concluída rapidamente”. Considero que é perfeitamente razoável em certos casos, tais como quando em desenvolvimentos de POC, um MVP, um bugfix urgente ou quando estamos a desenvolver algo descartável. A melhor solução é a que responde melhor aos requisitos.
É salientado que esta abordagem não é sustentável a longo prazo e qualquer engenheiro de software com alguns anos de experiência consegue confirmar isso mesmo empiricamente. “Com programação tática é quase impossível produzir um bom system design”, isto porque a complexidade se torna incremental. Manter esta abordagem a longo prazo fará o débito técnico aumentar cada vez mais e sistema acabará por ficar tão complexo que quase será necessário tirar um PhD para o entender (claro que é ironia… ou se calhar não…). E vai ainda mais longe dando a entender que a situação se acaba por se tornar uma pescada de rabo na boca: Prazos cursos farão com que não se priorize planeamento futuro, aumentando a complexidade e o débito técnico. Como o sistema vai ficando mais complexo, todo o tempo disponível será alocado a novas tarefas porque agora demoram 2x ou 3x mais e é necessário trabalhar em cima de um sistema complexo tornando-o cada vez mais complexo. Este ciclo repete-se indefinidamente. Como o autor diz: “A complexidade é incremental”.
Por outro lado, apresenta, em forma de solução ou correção a programação estratégica, que nada mais é do que perceber que código funcional não é suficiente. Manter um bom desenho do sistema é crucial a longo prazo. Para isso deve-se investir em tarefas de planeamento e desenho, seguindo as boas práticas de engenharia de software e não apenas “bater código”. Ao executar uma tarefa deve-se ter uma visão holística de todo o sistema e não apenas da tarefa em específico.
“Strategic programming requires an investment mindset. Rather than taking the fastest path to finish your current project, you must invest time to improve the design of the system. These investments will slow you down a bit in the short term, but they will speed you up in the long term”
Mais uma vez, é percetível empiricamente, que este é um problema bastante comum em softwares de larga escala. Estes problemas advêm da natureza do ser humano e da exigência e competitividade do mundo empresarial.
O autor refere que na maior parte das organizações existem desenvolvedores que ele apelida como “tactical tornados”, mas neste post vou chamá-los apenas de “tornados”. Os tornados são profissionais dedicados que implementam novas funcionalidades mais rápido que ninguém, focando-se apenas no que é necessário fazer para a terminar. Estes profissionais são geralmente vistos como heróis pela gestão das organizações, geralmente não técnicos. O facto é que conseguem manter sempre os seus KPI nas nuvens. Ninguém faz mais story points do que eles! Mas a que preço?
“They are rarely considered heroes by the engineers who must work with their code in the future. Typically, other engineers must clean up the messes left behind by the tactical tornado, which makes it appear that those engineers (who are the real heroes) are making slower progress than the tactical tornado”
É caso para dizer: Os verdadeiros heróis não usam capa, fazem refactoring a big balls of mud.
Quando estava a ler este capítulo lembrei-me de imediato da nova onda (ou não tão nova quando isso) do “vibe coding”
Quero frisar que este livro foi publicado em 2018, antes da disponibilização das ferramentas de AI generativa, mas foi baseado em décadas de experiência. É incrível como um livro escrito antes da popularização de ferramentas de AI generativa, viria a espelhar tantos dos desafios e dilemas que estas ferramentas trouxeram.
O vibe coding está a ganhar bastante popularidade com o advento das ferramentas de AI generativa. Basicamente, consiste em usar e estar dependente de um assistente de inteligência artificial para escrever código. Com esta abordagem é um facto que pessoas não técnicas, estudantes ou profissionais com pouca experiência, conseguem desenvolver aplicações funcionais relativamente rápido, sem grande esforço, dizendo apenas em linguagem natural, a aplicação que querem. Eu uso o lovable.ai para desenhar as UI dos meus projetos pessoais, mesmo ciente de todas as implicações que isso pode ter, porque na verdade, não gosto de codificar UI. Desde que experimentei esta e outras ferramentas do género, prefiro pagar alguns dólares para ter esse trabalho feito para os meus projetos pessoais.
Estamos a aperceber que este conceito está a ser cada vez mais utilizado pelos desenvolvedores e está a propagar-se de forma tão acelerada e a ser promovido como um conto de fadas que muitos stakeholders do mundo de desenvolvimento de software não esperam nada menos do que inteligência artificial generativa a escrever código e que os profissionais sejam capazes de entregar funcionalidades mais rápido fazendo com que as suas aplicações evoluam mais rapidamente do que nunca. Não digo isto baseado num contexto pessoal (felizmente), mas em muitas conversas e discussões com colegas do ramo, a maior parte de países diferente do meu. No Reddit pode-se notar que há uma divergência total de opinião entre os profissionais técnicos e os profissionais ligados à gestão. E porque é que isto está a acontecer? Bem, tenho uma teoria… As equipas de marketing destas aplicações, prometem e mostram maravilhas destas aplicações. Até nas redes sociais, instagram, Youtube, etc, começam a aparece influenciadores a propagarem esta prática e estas ferramentas. Dou um título de um vídeo que me apareceu como sugestão no Youtube: “Vou criar uma aplicação completa em menos de 10 minutos”. Obviamente, totalmente em vibe coding. Dá a sensação de que atualmente vale tudo para ter views ou para vender algo. Estas promessas estão a criar uma expetativa que poderá não se vir a realizar.
Quando estava a ler o livro e o autor introduziu a programação tática e os tornados, lembrei-me de imediato desta nova onde de levar o vibe coding ao extremo e da forma como esta abordagem e quem a aplica, os vibe coders, estão a ser vistos como heróis. “A história não se repete, mas frequentemente rima” - Mark Twain. Serão os vibe coders extremos, os novos tornados?
Com isto não estou a menosprezar os inúmeros benefícios da utilização de ferramentas de AI em todo o processo de desenvolvimento de software. Contudo, estar dependente de uma ferramenta para desenvolver um software é assumir um risco incalculado. Como se sabe, todas as inteligências artificiais são treinadas com dados, logo pode-se concluir que o código gerado é baseado em vários exemplos e repositórios que foram usadas para treinar o modelo. Assumindo que a qualidade do código disponível na internet, usado para treinar as AI, segue uma distribuição normal, o código gerado pelas ferramentas de AI será algo em torno de mediano. Assumindo também que a complexidade e documentação do codigo dos projetos segue a mesma distribuição, os resultados serão mesmo. Este raciocínio pode ser aplicado a outras métricas.
Outro problema é o contexto. Anteriormente, ao falar da visão de strategic programming de John Ousterhout, marquei a negrito a frase: “Ao executar uma tarefa deve-se ter uma visão holística de todo o sistema e não apenas da tarefa em específico”. É praticamente impossível dar todo o contexto holístico do processo de engenharia de software (planeamento, requisitos, design, arquitetura aos vários níveis, etc etc etc e em especial, os corner cases e decisões pouco normais para atender a um requisito específico ou estratégia), de uma aplicação complexa, para a IA generativa codificar uma nova funcionalidade ou bugfix, devido às limitações da mesma. Se alguém com conhecimento de IA leu a frase anterior, certamente pensou em sistemas RAG como potencial solução. O Copilot usa RAG para empoderar o seu contexto, mas o RAG tem diversas limitações, quando trabalha com um contexto diretamente com diversas ligações e dependências, como é o caso de um software. Contudo é excelente a trabalhar em contextos pequenos e delimitados. Este termo soou familiar (DDD)?
Vou divagar no pensamento:
Os tornados que estão a ler, certamente pensaram numa solução mais complexa, como criar uma chain para ler todos os documentos de requisitos, diversos agentes a trabalhar em conjunto, a debater entre eles, etc etc etc. Certamente também pensaram que os agentes também precisam de um contexto teórico (conhecimento/ fundamentos teóricos) e para isso devem te fine tunning ou até outro sistema complexo de RAG composto por diversos agentes. Como habitual num tornado, acabou por criar um sistema que ao início é capaz de mostrar o céu em forma de resultados, mas que na verdade é complexo extremamente caro e difícil de manter que acabará por mostrar-se infernal quando os requisitos e a complexidade do sistema evoluírem.
Então a IA generativa não pode ser usada no processo de desenvolvimento de um software que siga as boas práticas de engenharia de software? Pode e deve! Tendo em conta que se use como uma ferramenta de produtividade e pair programming, não de order programming (acabei de inventar o termo). Que seja um par e não o um escravo. Note-se que o slogan do Copilot é precisamente: “Your AI pair programming”.
Pessoalmente, uso em tudo! Desde brainstorm, análise de requisitos, repositório de informação e também geração e review de código. Às vezes dou por mim a debater ideias com o ChatGPT como se fosse meu colega de trabalho.
Tenho vindo a usar estas ferramentas e fico extremamente convencido de que no longo prazo ficará nítido que estas estão e irão continuar a potenciar os softwares bem desenhados, fazendo com que de facto sejam desenvolvidas funcionalidades e se acrescente valor mais rápido. Isto porque o engenheiro poderá alocar a maior parte da sua energia e tempo no desenho e planeamento do software e não na escrita de código. Contudo, é extremamente importante que os fundamentos estejam sempre consolidados para garantir que o código gerado por IA responde aos requisitos qualidade e se não responder, que o engenheiro seja capaz de o corrigir. É preciso evitar que o paradoxo da abundância de informação não torne os profissionais menos capazes. Como? Na verdade, não sei.... É certamente um dos maiores desafios da formação atualmente. Talvez seja ser um tema para outro post.
Por outro lado, estou também convencido que estas ferramentas irão fazer com que os softwares que usam abordagem táticas ou extreme go horse, que já são extremamente complexos, que já têm débito técnico e com baixa capacidade de manutenção, ficarão ainda piores. Uma ferramenta de IA é treinada com código standarizado e não consegue entender complexidade extrema, atalhos, truques, nem outras habilidades introduzidas pelos tornados. Por este motivo, gera o código que está no seu conhecimento, fazendo com que a solução fique ainda mais confusa e complexa, contribuindo para o aumento do débito técnico.
Acredito que no futuro, talvez próximo, os novos heróis reais serão necessários para resolver muita confusão criada por vibe coding dependente de AI.
Em suma, existe umpotencial enorme para potencializar soluções bem desenhadas, mas também para potenciar a confusão causada por tornados.
Quem me conhece sabe que que é meu costume fazer analogias e para terminar, aqui vai mais uma:
Ter uma enciclopédia de física e uma calculadora científica não faz de ninguém físico. Alguém com estes dois objetos pode facilmente chegar a algumas soluções para problemas do dia a dia, tais como a velocidade média da bola nos livres do Cristiano Ronaldo, a energia consumida por um eletrodoméstico novo ou até a distância de travagem de um carro (sim, tirando o do Ronaldo, estes exemplos são autoria de uma AI), mas dificilmente consegue chegar a conclusões e resolver problemas mais complexos que necessitem articulação e conhecimento profundo de vários conceitos fundamentais da física. O mesmo acontece em engenharia de software.
É o famoso exemplo de: “Não vais acreditar na aplicação que eu fiz! Vou-te mandar o link para veres: http://localhost:3000” .
Excerpt from Chapter 3:
Working Code Isn’t Enough (Strategic vs. Tactical Programming)
One of the most important elements of good software design is the mindset you adopt when you approach a programming task. Many organizations encourage a tactical mindset, focused on getting features working as quickly as possible. However, if you want a good design, you must take a more strategic approach where you invest time to produce clean designs and fix problems. This chapter discusses why the strategic approach produces better designs and is actually cheaper than the tactical approach over the long run.
3.1 Tactical programming
Most programmers approach software development with a mindset I call tactical programming. In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. At first glance this seems totally reasonable: what could be more important than writing code that works? However, tactical programming makes it nearly impossible to produce a good system design. The problem with tactical programming is that it is short-sighted. If you’re programming tactically, you’re trying to finish a task as quickly as possible. Perhaps you have a hard deadline. As a result, planning for the future isn’t a priority. You don’t spend much time looking for the best design; you just want to get something working soon. You tell yourself that it’s OK to add a bit of complexity or introduce a small kludge or two, if that allows the current task to be completed more quickly. This is how systems become complicated. As discussed in the previous chapter, complexity is incremental. It’s not one particular thing that makes a system complicated, but the accumulation of dozens or hundreds of small things. If you program tactically, each programming task will contribute a few of these complexities. Each of them probably seems like a reasonable compromise in order to finish the current task quickly. However, the complexities accumulate rapidly, especially if everyone is programming tactically. Before long, some of the complexities will start causing problems, and you will begin to wish you hadn’t taken those early shortcuts. But, you will tell yourself that it’s more important to get the next feature working than to go back and refactor existing code. Refactoring may help out in the long run, but it will definitely slow down the current task. So, you look for quick patches to work around any problems you encounter. This just creates more complexity, which then requires more patches. Pretty soon the code is a mess, but by this point things are so bad that it would take months of work to clean it up. There’s no way your schedule can tolerate that kind of delay, and fixing one or two of the problems doesn’t seem like it will make much difference, so you just keep programming tactically. If you have worked on a large software project for very long, I suspect you have seen tactical programming at work and have experienced the problems that result. Once you start down the tactical path, it’s difficult to change. Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado. The tactical tornado is a prolific programmer who pumps out code far faster than others but works in a totally tactical fashion. When it comes to implementing a quick feature, nobody gets it done faster than the tactical tornado. In some organizations, management treats tactical tornadoes as heroes. However, tactical tornadoes leave behind a wake of destruction. They are rarely considered heroes by the engineers who must work with their code in the future. Typically, other engineers must clean up the messes left behind by the tactical tornado, which makes it appear that those engineers (who are the real heroes) are making slower progress than the tactical tornado.
3.2 Strategic programming
The first step towards becoming a good software designer is to realize that working code isn’t enough. It’s not acceptable to introduce unnecessary complexities in order to finish your current task faster. The most important thing is the long-term structure of the system. Most of the code in any system is written by extending the existing code base, so your most important job as a developer is to facilitate those future extensions. Thus, you should not think of “working code” as your primary goal, though of course your code must work. Your primary goal must be to produce a great design, which also happens to work. This is strategic programming. Strategic programming requires an investment mindset. Rather than taking the fastest path to finish your current project, you must invest time to improve the design of the system. These investments will slow you down a bit in the short term, but they will speed you up in the long term, as illustrated in Figure 3.1. Some of the investments will be proactive. For example, it’s worth taking a little extra time to find a simple design for each new class; rather than implementing the first idea that comes to mind, try a couple of alternative designs and pick the cleanest one. Try to imagine a few ways in which the system might need to be changed in the future and make sure that will be easy with your design. Writing good documentation is another example of a proactive investment. Other investments will be reactive. No matter how much you invest up front, there will inevitably be mistakes in your design decisions. Over time, these mistakes will become obvious. When you discover a design problem, don’t just ignore it or patch around it; take a little extra time to fix it. If you program strategically, you will continually make small improvements to the system design. This is the opposite of tactical programming, where you are continually adding small bits of complexity that cause problems in the future.