Modern large language models (LLMs) are a new kind of power tool that disrupts software engineering. Our field has shifted several times before, but this one is a doozy. A conversation with a friend led to him asking “What would you encourage new computer science graduates or current CS students to focus on to make themselves successful?” It’s a natural question when it feels like a sea change is transforming the field.
The fundamentals haven’t changed. School time remains a time to learn fundamentals.
Learn to learn
Studying difficult courses and technical problems is important. These are as much about learning to learn tech as they are about getting a broad enough base of knowledge to be able to generalize to future technology and skills. For computer science, designing and working on compilers, distributed systems, databases, and complex multi-component systems is a good start.
Learn to debug
Debugging problems is the most scientific part of engineering. When something isn’t working, form hypotheses and test them. For non-trivial problems write the process down, working out for a given hypothesis what you’d need to find out to prove or disprove it.
It’s easy to fall into a trap of “flailing” – running commands to list details without a clear understanding of what you’re looking for, restarting or changing things without a clear idea of what it might do. It’s easy to spend a bunch of time and then occasionally make the problem go away without ever understanding what was actually wrong, which means you’ve learned nothing.
Being able to debug in a structured way is a superpower. It’ll look like magic to those who do not know how to do it, and have not built the intuition to form and test good hypotheses based on available data to narrow down to a cause. The fix is often the easy part once the cause is understood.
Start, define, and finish projects
Personal projects and “undefined” problems are valuable because turning a “problem” into a solution when the problem is not well defined is a very important skill and not often taught directly in classes. There’s a big difference between “implement the solution to a problem set” and “I want to know when the laundry machines are available to use”.
Classwork often has a crisp, well-defined finishing point, but personal projects (and real products!) require figuring out what success looks like. Solving a real-world problem means first defining what you’re solving. The process of prioritizing a wish list of clever ideas, making and executing a plan, and iterating to change course when necessary is useful. It’s very good to have done it before hitting your first job.
Learn to communicate
Learning to communicate clearly is important and a subset of learning to think clearly. Working out how to communicate ideas with the right level of context and detail for a given audience takes practice. You’ll need to write a lot of things: proposals, decisions, technical designs (which means thinking them through well enough to write them down) and it takes practice to be good at it. Start early.
Learn to work with people
School and being fresh into a career is an excellent time to develop and practice the skills needed to work with people – this includes communication but also means things like understanding when prioritizing improving a relationship is better than a particular decision going your way.
Over the long term, relationships determine career success.
Learning how to keep working effectively even if you don’t agree with every decision, and understanding when to hold your ground on a decision and when to “disagree and commit” are critical skills for senior engineering roles.
True technical consensus is relatively rare, and teams that always seek it are spending too much energy on many decisions. Indeed, if there is too much consensus on a team it may be a signal some additional viewpoints are needed.
Learn to retrospect
Take the time to review to understand what went right, what went awry, and extract what insight you can about how to improve. Don’t dwell on the past, but do spend enough time learning to avoid repeating it.
Reflect on past decisions with empathy. Analyze choices based on what you knew at the time. Identify missed signals that could have led to better outcomes. Ignore foreknowledge since assuming you could know the future doesn’t help you make better decisions. Avoid blame. Retrospect to get better, only.
Develop taste
Software architecture is composable if it forms good building blocks for assembling solutions. Composable software architecture is a force multiplier for building, testing, and evolving systems. Solutions built using composable software are more easily modified with parallel work, which means improvements to them can be scaled. Stable components can be built to a high quality with a test suite that provides confidence they perform correctly.
Developing taste for software architecture and code is important to becoming a good engineer. A lot of how to define a system in terms of components and the interfaces between them is a matter of art rather than rules, and is going to be informed by experience and requirements.
Work to develop an intuition for why something is good and develop the ability to have a strong opinion, defend it with reasoned arguments, and still be open to changing your mind when you learn new information. It’s easy to form an obstinate strong opinion, and it’s easy to let someone else make the decisions, but developing leadership means developing the ability to form, defend, and change opinions.
Initially every decision feels critical, but understanding the difference between critical “one-way” decisions and “reversible” decisions is important to develop a sense for how much effort a given decision is worth. Avoid spending more effort on a decision than getting it wrong or reversing it might cost.
Software design and good decision making are guided by taste and intuition. The only effective way to get better at both is practice and reflection.
Interesting times
These are all evergreen skills. Technology evolves, fashions shift, but knowing how to learn, debug, communicate, work with people, and design systems will always be useful.
Software engineering has had several sea changes over the (very) few decades of its existence. From the initial shift to being software from systems being hard-wired, through machine code, assemblers, and a series of higher level languages to now LLM-powered agents generating code, the core of what we do is solve problems, together.