0% found this document useful (0 votes)
384 views40 pages

Fundamentals of Software Engineering - Nathaniel Schutta and Jakub Pilimon

The document is an early release of 'Fundamentals of Software Engineering' by Nathaniel Schutta and Jakub Pilimon, aimed at new software developers transitioning from coding to engineering. It addresses the gap between formal education and the practical skills needed for success in the field, providing insights on essential skills, code reading, and career advancement. The book serves as a roadmap for both new and seasoned developers to enhance their understanding of software engineering fundamentals.

Uploaded by

sccm.elibrary
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
384 views40 pages

Fundamentals of Software Engineering - Nathaniel Schutta and Jakub Pilimon

The document is an early release of 'Fundamentals of Software Engineering' by Nathaniel Schutta and Jakub Pilimon, aimed at new software developers transitioning from coding to engineering. It addresses the gap between formal education and the practical skills needed for success in the field, providing insights on essential skills, code reading, and career advancement. The book serves as a roadmap for both new and seasoned developers to enhance their understanding of software engineering fundamentals.

Uploaded by

sccm.elibrary
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 40

Fundamentals of Software

Engineering
From Coder to Engineer

With Early Release ebooks, you get books in their earliest form—the
author’s raw and unedited content as they write—so you can take
advantage of these technologies long before the official release of these
titles.

Nathaniel Schutta and Jakub Pilimon

OceanofPDF.com
Fundamentals of Software Engineering
by Nathaniel Schutta and Jakub Pilimon
Copyright © 2023 O’Reilly Media. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc. , 1005 Gravenstein Highway North,
Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales
promotional use. Online editions are also available for most titles
(http://oreilly.com). For more information, contact our
corporate/institutional sales department: 800-998-9938 or
[email protected].

Editors: Virginia Wilson and Louise Corrigan

Production Editor: Aleeya Rahman

Interior Designer: David Futato

Cover Designer: Karen Montgomery

Illustrator: Kate Dullea

May 2024: First Edition

Revision History for the Early Release


2023-08-11: First Release
See http://oreilly.com/catalog/errata.csp?isbn=9781098143237 for release
details.
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc.
Fundamentals of Software Engineering, the cover image, and related trade
dress are trademarks of O’Reilly Media, Inc.
The views expressed in this work are those of the authors and do not
represent the publisher’s views. While the publisher and the authors have
used good faith efforts to ensure that the information and instructions
contained in this work are accurate, the publisher and the authors disclaim
all responsibility for errors or omissions, including without limitation
responsibility for damages resulting from the use of or reliance on this
work. Use of the information and instructions contained in this work is at
your own risk. If any code samples or other technology this work contains
or describes is subject to open source licenses or the intellectual property
rights of others, it is your responsibility to ensure that your use thereof
complies with such licenses and/or rights.
978-1-098-14323-7

OceanofPDF.com
Preface

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the
author’s raw and unedited content as they write—so you can take
advantage of these technologies long before the official release of these
titles.
This will be the Preface of the final book. Please note that the GitHub
repo will be made active later on.
If you have comments about how we might improve the content and/or
examples in this book, or if you notice missing material within this
chapter, please reach out to the editor at [email protected].

There are many paths to become a software developer, from undergraduate


computer science programs to intensive boot camps where you teach
yourself. Early in your career it can feel like you’ve just taken your first
steps into a larger world. It can be overwhelming. We know, we’ve been
there! Regardless of your background, if you’re a newly minted practitioner
you soon discover there is a vast array of critical topics you weren’t taught
and skills you don’t have, which keeps you from advancing to more senior
roles.
Figure P-1. There is a gap between what developers are taught versus what they need in order to be
successful on projects.

There is a gap between what you learn in a boot camp or a computer


science degree and what we need you to know to be a successful engineer.
(Technical companies like ThoughtWorks have stringent hiring practices,
yet they still send their new hires through a boot camp; and they aren’t
alone. ) This book attempts to bridge that gap by giving you the context and
grounding you need to chart your own career path, and helping you identify
opportunities for personal and professional growth. Think of it as an
onboarding guide for the new software engineer.
Who This Book Is For
Ultimately, this book is targeted at new developers, and we wrote it with
them foremost in our minds. We hope this book will show you the full
picture of what it means to be more than a “coder”, to evolve into a
software engineer and, more importantly, to help your career advancement.
But it isn’t just newly minted developers who benefit from mastering the
fundamentals. This book will also help more seasoned developers advance
beyond their first job and grow their career to a senior role. After all, the
strength of your skill set is usually what determines whether you’ll be given
more responsibilities and promoted.

What You Will Learn


What are the skills newly minted developers need to succeed and thrive as
software engineers? What separates the beginner from the experienced
engineer? From reading code, to writing code that’s readable, to testing, to
work life balance, to learning to learn—we will tell you everything you
need to know (and even some things you didn’t know you needed to know).
Most developers learn these things through trial and error, sometimes
costing their projects dearly. But it doesn’t have to be that way! These skills
are the fundamentals of software engineering that will set you up for a
successful career and—unlike the flavor of the day framework or the trendy
language—these skills will last a lifetime.
This book should act as a roadmap for the newly initiated technical
practitioner. While it won’t be an in depth guide to any one topic, it will
show you the universe of topics involved in software engineering so you
have enough information to understand the basic concepts. We recommend
lots of great resources so you can do a deep dive on the topics that interest
you. Most readers will initially engage with this book cover to cover, and
then we suspect you will return to certain chapters for pointers as you move
from project to project throughout your career and encounter newly relevant
topics.
OceanofPDF.com
Chapter 1. Reading Code

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the
author’s raw and unedited content as they write—so you can take
advantage of these technologies long before the official release of these
titles.
This will be the 2nd chapter of the final book. Please note that the
GitHub repo will be made active later on.
If you have comments about how we might improve the content and/or
examples in this book, or if you notice missing material within this
chapter, please reach out to the editor at [email protected].

Despite the way coding is taught, developers spend far more time reading
code than writing it. In most beginner coding courses, you jump
immediately into writing code, focusing on core language concepts and
idioms without acknowledging that you’d never learn Polish or Portuguese
in a similar manner. And while most academic projects start from a blank
slate, practicing developers are almost always working within the confines
of code that has taken years to arrive at its current state. While it may not be
your first choice, you will work with code you did not write. Take heart,
there are techniques to help you orient yourself in a new codebase.

Working With Existing Code


“Some of the most valuable experience I gained was from supporting a
legacy app. I highly recommend it but I wouldn’t wish it on anyone.” —
Dalia Shea
If you think about the things you like least about your job, obvious targets
like too many meetings and impossible deadlines will likely top your list,
but odds are reading existing code will crack the top ten. Whether you
learned to code in a bootcamp or on a college campus, you probably never
had a course (or even a lecture) about how to effectively read code. Instead
you spent much of your educational time in the blissful space known as
“greenfield development” unencumbered by the baggage of the past. Yet
you’ve likely had vanishingly few opportunities to build an application
from a blank editor in your professional life.

TERMS MATTER
Reading old code is often the stuff of nightmares for most developers,
to the point that we often use the term legacy code to describe it. This is
usually not meant as a compliment. However, you shouldn’t disparage
the success of an existing application. If a product has delivered
business value for years and has justified continued investment, that is
worthy of a pat on the back. We prefer a more positive frame such as
heritage code or existing code. For a more in depth discussion of the
topic, please see Chapter X.

It isn’t surprising that many developers loathe reading other programmers’


code. When you encounter existing code, you actually have three problems
to solve:

First and foremost, you need to understand the domain, the problem
you are trying to solve. And the domains developers work in are very
demanding! Software is eating the world, meaning software engineers
are tasked with increasingly more challenging business contexts; much
of the proverbial low hanging fruit has already been picked. But that is
only one part of the problem when dealing with existing code.
Second, you must see the problem through the eyes of the developer
who came before you, and that is often the most challenging aspect of
software development.
Third, it’s possible the code isn’t the right abstraction. Perhaps it is
modeled in a way that is too generic or fails to capture the proper
nuance of the domain.

It also doesn’t help that you are almost always dealing with patches on top
of patches. Maybe the last developer didn’t have a full understanding of the
problem or they weren’t up to speed on some new language feature that
could greatly simplify the job at hand. Add in the typical demands of fix it
fast, and you could spend an afternoon deciphering a single method.

Cognitive Biases
Of course you don’t write bad code, do you? On more than one occasion
your humble authors have struggled with some code, uttering less polite
variations of “what idiot wrote this” only to discover that it was actually
written by none other than ourselves. And frankly, if you read code you
wrote a few years ago, you should be a little disappointed—that’s a sign of
growth; you know more today than you did then. That is a good thing!
You also have a couple of cognitive biases working against you when you
work with existing code. First is the IKEA effect which says in a nutshell,
you place a higher value on things you create. One study found people
would pay 63% more for a product they successfully assembled themselves
versus the identical product put together by someone else. There are
actually several examples of companies profiting off the IKEA effect! If
you’ve ever picked your own strawberries or apples, you are often paying a
premium to, well, do some of the work yourself.
Additionally there is the mere exposure effect: you tend to prefer the things
you are already familiar with. Which leads to the typical dogmatism many
developers have around programming languages. Developers tend to think
time began with whatever language they learned first. When Java first
introduced Lambda expressions, someone on a language-specific mailing
list asked why Java needed these “new fangled Lambdas” not realizing
Lambdas are not a new concept in programming languages and were part of
the original plan for Java itself!
Developers can be very provincial around their preferred tools, which is
something Paul Graham touches on in his essay Beating the Averages.
Graham says programming languages exist on a power continuum, but you
often can’t recognize why a language is more powerful than another. To
demonstrate his point he introduces the hypothetical Blub language and a
very productive Blub programmer. When the Blub programmer looks down
the power continuum, all they see are languages that lack features they use
everyday, and they can’t understand why anyone would choose such an
inferior tool. When they look up the power continuum all they see are a
bunch of weird features they don’t have in Blub, and they can’t imagine
why anyone would need those to be productive since they aren’t in Blub
and they are a very good Blub programmer!

Learning New Languages


Think about learning a foreign language. For months, even years, you will
translate that language into your native tongue. Eventually you will think,
even dream, in your new language. But it takes time. Programming
languages are no different. Well, they have very demanding grammar rules
and far fewer keywords. Developers tend to get very attached to their first
language. Be careful here. Programming languages are just tools. Just as a
hammer is a better tool for pounding in a nail than a screwdriver, some
languages are better fits for certain problems than others. Make it a habit to
look at, explore and learn other languages. Some influential people suggest
learning a new language every year or two.
PROGRAMMING IS FUNDAMENTALLY ABOUT
COMMUNICATION
Before computer science departments started springing up at
universities around the world, programming often lived in the maths
department, and many assumed mathematical aptitude was a
prerequisite for success in software. Research shows that language
aptitude is a far better predictor of how quickly someone picks up a new
programming language than skill in mathematics. While certain
domains may be very maths heavy, the art of programming isn’t.
Programming is first and foremost a communication activity—and not
between the coder and the compiler. Don’t forget, the computer
understands any code (at least if it’s syntactically correct), but that
doesn’t mean a human will follow what you’re trying to accomplish.
The best software engineers focus on the person reading the code.

Learning a new language takes time, how do you justify the investment it
takes learning something you might not use daily at work? Learning a new
language will change how you code even if you don’t get to use the new
tool day in and day out. When you seek out a new language challenge, try
and pick something that is different from what you use at work. If you’re an
experienced Java developer, look beyond other C like languages such as C#
(not that there’s anything wrong with learning C# mind you) towards
different paradigms. Consider instead a dynamic language like Ruby or a
functional language like Haskell. Trust us, even just a cursory examination
of a language outside your normal neighborhood will fundamentally alter
your approach to programming. You may come to appreciate your regular
language more, or you may find yourself writing code in a different way.
Take time to learn new things.
Learning new languages gets easier over time. The more languages you
know, the more you have to compare to. Think back to the first language
you learned—you were starting at zero. By the third, fourth, fifth language,
you start to see how one idiom is just like another one from Java, or how
some structure was borrowed from Ruby.

Approaching a New Codebase


As much as you may wish you could spend all your work hours focused on
crafting new code, you will encounter existing code bases throughout your
career. How can you get up to speed on a new project without losing your
mind? First, start with your teammates. A basic project overview should be
part of any onboarding experience. Don’t be afraid to spend some time with
the documentation. You could learn more in a few minutes with the docs
than hours with the debugger. If your project’s documentation is out of date,
update it as you learn; if it is nonexistent, consider building your own as
you go.

THE GOLDEN RULE


Odds are, early in your educational career, you learned about the
Golden Rule which says you should treat others the way you would like
to be treated.1 This principle is embraced across continents and dates
back thousands of years as a basic tenet of how to live in civilized
society. But it turns out, it isn’t just a pithy ethical principle. You can
(and should!) apply the same standard to your code.

With the Golden Rule in mind, write your documentation for those who will
come after you. Favor light weight, low ceremony approaches seeking to
answer common questions such as:

What does your service do?


How does it work?
What does it depend on?
Wait, we can hear you now: documentation may be (and often is) out of
synch with the code. But believe us—that doesn’t actually have to be the
case. Documentation can evolve with the code. The best way to ensure that
it does is to use tests as documentation. Tests written with behavioral driven
styles, if written properly, can produce executable documentation, a topic
discussed at greater length in Chapter X.

WARNING
Metrics Can Mislead
Code coverage (how much of the code base is executed when the tests are run) can be a
very useful metric on a project. However, there are no silver bullets in software, and it is
possible to fail even with 100% code coverage. A friend of ours joined a project that
was having regressions with every release. As he was getting up to speed on the code he
asked the tech lead if there were any tests. The tech lead very proudly said, “yes, we
have right around 92% code coverage.” Very impressed, our friend was somewhat
surprised they had so many regressions but he continued his analysis.
Looking at the test code he found some startling patterns. At first he thought these were
isolated, but eventually he discovered they were endemic to the code base. He went
back to the tech lead and said, “I couldn’t help but notice your tests don’t have any
asserts.” The tech lead responded by repeating the code coverage statistic.
The meta lesson is: be wary of any metric, because they can mislead. But don’t lose
sight of the value and purpose of a practice. If it is just about ceremony, you are unlikely
to get the benefit you expect. Project teams should regularly challenge themselves and
their approach; don’t be afraid to change course when warranted.

Software Archeology
Once you’ve surveyed the team and familiarized yourself with any existing
documentation, it is time to open your editor of choice and practice some
software archeology. Roll up your sleeves and root around in the code base!
To paraphrase Sir Issac Newton, look for smoother pebbles and prettier
shells. Look at the code structure—how is the code organized? Some
languages have first class constructs for packaging code, others rely on
conventions. How does the code fit together? What domain concepts are
expressed in the code? Read the tests—what do they tell you about the
functionality?
Once you have your bearings, run the application. What does it do? Find a
specific element, be it something on a user interface or a parameter to a
service call, and map that back to the code. Hunt for a landmark; if you
know a given action results in an update to the datastore, find that in the
code. Use your debugger to walk though the code—did it work the way you
anticipated? Did you end up on a vastly different code path? Ultimately,
you are building a mental model of the code, you are loading it into your
brain.

NOTE
“The goal of software design is to create chunks or slices that fit into a human mind.
The software keeps growing but the human mind maxes out, so we have to keep
chunking and slicing differently if we want to keep making changes.” —Kent Beck

Use your editor to navigate the code. Many editors make it very easy to
jump to methods in other classes as you work your way through the code.
Consider collapsing all the method bodies to give you a smaller surface area
to peruse (see Figure 1-1). Read the method names. What does that tell you
about the purpose of the module?
Figure 1-1. : Collapse methods to orient yourself in the codebase

Do not assume the code does what the name implies. Naming things is hard
and, as code evolves, variable and method names may no longer reflect
reality. Don’t rush, confirm your hunches. It is tempting to cut corners, but
take your time.2 Exceptions can mislead. More than once we have
encountered exceptions that made incorrect assumptions about possible
error conditions.
Figure 1-2. : Use your IDE to help you navigate code

Use your source code management tool as well. Many modern tools allow
you to quickly move about your project. Look at the change history of the
files. What changes frequently? What do the commit logs tell you about the
updates? Start with the most frequently modified classes, something git can
show you with a command like this:
git log -pretty=format: --since="1 year ago" --name-only -
"*.java" | sort | uniq -c | sort -rg | head -10
Who on your team made the most recent modification or the most frequent
changes? Don’t be afraid to reach out to your teammates with questions!
Figure 1-3. : Use your source code management tool to help you understand the code

Practice Makes Perfect


At the end of the day, practice some grace with yourself. Modern code
bases are often sprawling. One person cannot understand it in its entirety,
and that isn’t the goal. Your knowledge will grow over time. Rinse and
repeat the process as you encounter new parts of your project. It can be
intimidating, but every developer has gone through it. You will be fine!
How do you improve your code reading skills? As much as you may dread
it, practice reading the code. There are so many well written, publicly
available, open source options in a variety of languages for you to choose
from. There aren’t any shortcuts; you cannot improve without practice. It
does get easier over time, and you will get faster.
WHAT ABOUT AI?
Like any discipline, software tooling continues to evolve. From shell
based text editors to IDEs with integrated refactoring tools and
IntelliSense code completion, writing code has gotten easier. Of course
the size and complexity of the applications you’re working on has also
grown so perhaps it’s a wash! With the advent of things like GitHub
Copilot and generative AI like ChatGPT, some have even suggested
developers will soon be replaced. The rumors of the end of developers
are often grossly exaggerated, from COBOL to fourth-generation
programming languages to various drag and drop “programming”
solutions failing to result in large scale reduction in the demand for
software engineering.
These tools have a place in your toolbox—for example, you can ask
ChatGPT what a chunk of code does3 but you still need to understand
the context. AI doesn’t know the purpose of a given class, so you still
need to read the code, wade through the documentation and talk to your
fellow developers.

Wrapping Up
Arguably, coding is taught backwards: you learn to write before you learn
to read, and yet you will spend a significant amount of your career reading
code written by someone else. While you may not enjoy existing code as
much as greenfield development, it comes with the paycheck. Rather than
run from the situation, learn to embrace it; there is much to gain
professionally. Be aware of cognitive biases. Don’t be afraid to roll up your
sleeves and root around in an unfamiliar codebase—you will learn
something. As your understanding grows, leave the code better than you
found it, easing the path of the next developer...which just might be you!
Additional Resources
Code as Design: Three Essays by Jack W. Reeves
Reading Code Is Harder Than Writing It
Reading Other People’s Code
How to quickly and effectively read other people’s code
How to read code without ripping your hair out

1 Or more commonly “Do unto others as you would have them do unto you.”

2 In other words, take the time it takes so it takes less time.

3 Though your organization’s lawyers likely have strong opinions about the use of such tools,
double check your corporate policies before you paste your pricing algorithm into one of them!

OceanofPDF.com
Chapter 2. Writing Code

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the
author’s raw and unedited content as they write—so you can take
advantage of these technologies long before the official release of these
titles.
This will be the 3rd chapter of the final book. Please note that the
GitHub repo will be made active later on.
If you have comments about how we might improve the content and/or
examples in this book, or if you notice missing material within this
chapter, please reach out to the editor at [email protected].

Writing code is, without a doubt, a very important part of software


engineering. And while the act of coding is widely taught, the nuances of
writing good code aren’t as evenly distributed. Just because you can write
code to solve a problem, doesn’t mean you should write code to solve a
problem! For better or worse, developers often have very strong opinions
on what constitutes good code or bad code, but metrics and tooling can give
you insights and guidance. Tests are some of the best documentation money
can buy. Code reviews, done well, can ensure your team doesn’t rely on
error prone forms or overly clever code. Ultimately, code should be written
to be read.

Don’t Reinvent the Wheel


Before you start pounding out line after line of (undoubtedly excellent)
code, take some time to see if the problem at hand has already been solved.
Developers often write too much code, solving problems that others have
already worked out. Before cranking out some fresh code, look around. Is
there a library you could leverage? Is there a language feature you could
use? If you think there should be a better way, there just might be.
THE CAPITALIZATION ASSIGNMENT
Nate here. Early in my career, my tech lead gave me a very simple task:
update the billing address routine to capitalize the street addresses. She
pointed me to the code I needed to update and told me about a utility
function someone had written years before to capitalize strings. I took
one look at the homegrown function and, to put it mildly, it was a mess.
The method signature should have taken one argument (the string to
capitalize); instead it had more than a dozen. Rather than spend hours
deciphering a gnarly function, I decided to do a quick search and see if
there was a better option; capitalizing strings seemed common enough
that it was probably a language feature or at a minimum part of an
existing utility library.
Unsurprisingly, the language had built in string capitalization, which I
then proceeded to use. At our team meeting the following week, we
were talking about what we’d done the week before and so I described
my task. One of the senior engineers turned to me and asked if I used
the bespoke utility function. I replied that I had not, I just used what the
language offered. Cue record scratch moment. Every head turned to
face me, the least experienced developer in the room; and the only one
who knew string capitalization was a language feature. I don’t know
when it was added to the language, but it taught me several important
lessons.
First of all, if you think there should be a simpler way, there probably
is. Taking even a few hours to investigate options and alternative
solutions could save you days or weeks of effort. Second, never
underestimate the value of a fresh set of eyes. New people—whether to
the codebase, the team or the software industry—often see things with a
fresh perspective, unencumbered by decisions that have outlived their
usefulness. Lastly, “that’s how we’ve always done it” is the most
dangerous phrase you will hear on a software project.
Yesterday’s best practices are often tomorrow’s antipatterns, and just
because something was the right thing to do five or ten years ago
doesn’t mean it is still the right thing to do today. Never be afraid to ask
why, to challenge the status quo. You may find yourself mindlessly
copying patterns or approaches you find without fully understanding
why. Software moves pretty fast; if you don’t stop and look around once
in while you could miss some really important things. Languages,
frameworks, technologies and techniques are constantly evolving,
keeping up is par for the course as a software engineer. Ask probing
questions and keep a weather eye on the horizon.

What is Good Code?


Of course there won’t always be a library or language feature to solve your
problem. You will, of course, write code! And you may ask yourself, well,
what is good code? To paraphrase Potter Stewart, good code can be hard to
define but you know it when you see it. Admittedly, good vs bad code can
be a very subjective concept. After a while you start to develop a sense for
it and you may even say code has a smell to it. For example, overly long
methods are generally a bad thing, but you still need to examine the code;
there may not be a simpler approach. Few developers will ever admit to
writing bad code, but anyone who’s ever opened an editor is guilty of some
less than stellar software.
Metrics can provide insight into your codebase. For example, cyclomatic
complexity can tell you how many there are within your source code, with
more paths indicating more complex code that would likely benefit from
refactoring. Many languages have source code analyzers like PMD,
SonarQube, and JSHint which can help prevent certain types of bugs and
bad coding practices from infesting the source code. Tools like SonarQube
and CodeScene can provide invaluable insight into your codebase. Odds
are, your organization has something. If you’re not sure, ask around. If they
don’t, why not spearhead the effort to bring one into your project?
While metrics should never be blindly followed, for example, some
complexity just can’t be avoided! You must apply common sense to any
rule of thumb because, in some instances, applying the common rule may
actually make things worse. However, you can take advantage of the
Hawthorne effect which says people modify their behavior when they’re
observed.1 You can absolutely use that to your advantage on a project! For
example, if you want more of the code to be covered by tests, prominently
display the code coverage stats.

INCREASING CODE COVERAGE THROUGH METRICS


Nate here again. Many years ago I joined a project that had been
churning out code for a few months. I was pleasantly surprised there
were, in fact, tests but I was disappointed that all but a small handful
were actually skipped by the build because they “kept failing”. Not to
be deterred, I ran a code coverage report. Unsurprisingly, it was a single
digit number. But I posted it and I talked about it. A lot. During most
standups and retrospectives. Whenever the number ticked up, I heaped
praise on my awesome teammates. When someone added tests to a
particularly tricky part of the code, I had them discuss how they did it.
Slowly but surely, our code coverage number went up and the code
became less brittle. It took several months, but by the time I moved on
the next project the code was closing in on seventy percent coverage.
By no means were we satisfied, but we were in a far better place. And it
all started by running—and then promoting—a simple code coverage
report.

Metrics can be abused or misused (see <<misleading-metrics>>). For


example, many organizations have tried to evaluate technical staff by lines
of code written or deleted or modified. Use them wisely. Consider external
coupling of a class as another example. In general, if you see a high degree
of coupling, you would consider that an opportunity for refactoring. What if
that class is actually a facade that is essentially hiding a set of classes? You
must contextualize any metric.
Just because you can measure something doesn’t mean it will provide
meaningful insights. Try to link metrics to goals and focus on trends. Are
you getting better or worse over time? Favor short time horizons, and be
ready and willing to adjust and adapt.

Less is More
With the size of some code bases you might think developers are paid by
the character. While some problems genuinely require millions of lines to
solve, in most cases you should favor smaller code bases. The less code, the
less there is to load into people’s brains. Many projects reach the size where
it is no longer possible for one developer to actually understand all of the
code, which is one of the forces that has given rise to microservices and
functions as a service.

NOTE
“The goal of software design is to create chunks or slices that fit into a human mind.
The software keeps growing but the human mind maxes out, so we have to keep
chunking and slicing differently if we want to keep making changes.” —Kent Beck

The typical big balls of mud often have dictionary sized Getting Started
guides, and build processes that are measured in phases of the moon. It can
take developers new to the project weeks or months to get productive
within the code. The smaller the code base, the less time it takes for a new
developer to get their head wrapped around the code and the faster they can
start contributing, see Chapter X Working With Heritage Code for more
details.
THE 0TH LAW OF COMPUTER SCIENCE
Many of the practices software engineers espouse in an effort to tame
code boil down to the 0th Law of Computer Science: high cohesion,
low coupling. Cohesion is a measure of how things relate to one other.
High cohesion means essentially that like things are together. The
notification function that also contains print logic would be an example
of low cohesion. Coupling refers to the amount of interdependence
between modules or routines. Code with tight coupling can be difficult
to modify as changes to one part of the code unexpectedly affect other,
seemingly unrelated parts of the system. Changing the notification
service shouldn’t break the print module.
High cohesion and low coupling tend to result in code that is more
readable and simpler to maintain and evolve. Many patterns are ways of
achieving high cohesion and low coupling, often at different levels of
abstraction. At their best, arguably, microservices are high cohesion,
low coupling applied to services.

Boilerplate code, even if it is generated by an editor or a framework, should


be avoided. While you may not have to write it, you will still carry it around
for the lifetime of the project. Classes should be short, a few pages or less.2
Programming languages will impact your definition of short, as some
languages are more verbose than others. In general, if you have to scroll,
your class might be too long.
WARNING
Favor composition over inheritance
Many languages allow classes to inherit from other classes, and while this feature can be
very powerful it tends to be overused. While there are certainly “is-a'' relationships (a
cat is-a mammal) in software, inheritance is often overused. Some developers use
inheritance as a reuse mechanism. Reuse is a byproduct, not a rationale! Let’s look at a
concrete example. Say your domain involves cars and trucks. You might create a vehicle
superclass that cars and trucks both descend from that includes a combustion engine—
since all cars and trucks have a combustion engine, defining it on the super class ensures
all cars and trucks also have a combustion engine. For example, see this Ruby pseudo
code:

Class Vehicle {
Engine engine
Number num_wheels
Number num_doors
Function brake {}
Function accelerate {}
}
Class Truck < Vehicle {
Number tow_capacity
}
Class ElectricVehicle < Vehicle {
Number range
# wait… EVs don’t have engines…
}

But along come electric vehicles with nary a combustion engine to be found. An EV is-a
vehicle, but not like those other vehicles. Composition is more flexible and should be
favored over inheritance. That isn’t to say you should never use inheritance, just that
you should prefer composition.

Write short methods, as in single digit lines of code. Like a Linux or Unix
command line tool, methods should do one thing and only one thing, and
they should do it well working in concert to accomplish larger goals. Any
method name that includes conjunctions (and, or, but) is a sign the method
is doing too much. Method names should be clear and concise and avoid
being clever. If you are having a hard time naming a method, it might be
doing too much. Try breaking it apart and see what happens. Be descriptive.
Remove logic duplication, even in small amounts. Simplify, then simplify
some more.
Write Code to be Read
There are any number of guidelines you can apply when it comes to the
“correct” length of a function from reuse to picking an often arbitrary
number of lines. Martin Fowler offers sage advice: mind the separation
between intent and implementation. In other words, how long does it take
for you to understand what a function is doing? You should be able to read
the name and understand immediately what the code does without
investigating the method body itself. This principle will often lead to
functions with only a few or even just a single line of code; make the
intention clear.
Code can be written like a newspaper article with an “inverted pyramid”.
Articles start with the lead then move to key facts and then on to deeper
background. You as the reader can stop at whatever level of detail you wish
—maybe just the first couple of paragraphs, maybe all the way to the end.
As they say in publishing, don’t bury the lead.
Code should follow the same model. The class name is almost like the
headline of an article. From there you should be able to skim the method
signatures to get a general understanding. If you want to explore a function
or a call to another class you are free to do so, but you should still
understand the gist of the code.
NAMING THINGS IS HARD
Every developer has stared at their editor struggling to name a variable,
method or class. And while this struggle can indicate an insufficient
understanding of the problem or some overly complex code, there is a
reason foo, bar and foobar are such common occurrences. It can be time
consuming to come up with meaningful names. Don’t rush!3 It is worth
the effort to come up with good names. Don’t be afraid to reach out to a
teammate for their input. Sometimes just explaining what you’re
working on will be enough to inspire the perfect moniker.
Don’t hesitate to refactor poorly named code. Modern editors have
powerful tools that make renaming a straightforward endeavor. Just
make sure you aren’t constantly renaming core domain concepts,
because that usually indicates a problem of misconception.
It may also help to play the gibberish game. Often, the first word you
use to define a concept isn’t the best option but it may shape your
thinking about the problem domain. When you are first working
through the domain, make up a word! After you’ve done some
additional analysis, go ahead and replace the gibberish with real words
—you’ll likely have some up with something that’s very different, and
clearer, than your first reaction. The next time you’re really stuck on
what to call something, throw in some nonsense words and circle back.

The Problem With Code Comments


Every programming language has some facility for a developer to speak,
not to the compiler or runtime but to their fellow developers via a code
comment. And while it may seem like one should liberally comment their
code, arguably doing so is a code smell.4 Code comments violate the DRY
principle, aka Don’t Repeat Yourself. While you will most often see DRY
violated with code that is copied and pasted or littered with logic
duplication, comments can also be problematic. In most cases you wrote the
code and then you turned around and wrote about the code.
Code comments can be a maintenance headache as well. You modify the
code, but will you take the time to update the comments too? Often the
comments begin diverging from the code, adding another layer of sediment
to the developers who encounter the code months or years later. Code
should be written to be readable. Your time is better spent making the code
simpler to read than in documenting what you did. Expressive languages
definitely aid in achieving readable code, and you may want to avoid the
more nuanced “magic” features of your language of choice.
Arguably the least useful code comment is the code change blocks that were
often the first few hundred (or thousands) of lines in a file. And while it can
be an interesting waypoint along your archeological journey, the repetition
of tracking numbers, dates, names and paragraphs about the change adds
noise to the process. Let your source code management tool do its job.
That isn’t to say code should never have comments.5 If you are doing
something that isn’t obvious, and you couldn’t find a way to simplify the
code, comments explaining the situation can be helpful. Providing context
around a choice of one algorithm over another or explaining a particularly
complex bit of logic can be helpful as well. That said, if code needs to be
explained, rewrite it.
Comments can serve as reminders to our future selves, or as a warning that
a hack works but you don’t (yet) understand why it does. Some developers
leave comments as warnings to future developers, like in the example
below.

// Dear maintainer:
//
// Once you are done trying to
//’optimize’ this routine,
// and have realized what a terrible
// mistake that was,
// please increment the following
// counter as a warning
// to the next person:
//
// total_hours_wasted_here = 42

Tests as Documentation
If comments aren’t an appropriate way to document code, what should you
do? Write tests. Tests, especially those written in more fluent styles, are
executable specifications that evolve along with the production code.
Documentation, whether code comments, readmes or specifications, tends
to diverge from the code as soon as it’s written. Tests written while you
write code allow you to refactor freely and increase your confidence in the
quality of your application. They also act as signposts for the developers
that follow you. Testing is explored in more depth in Chapter X Automated
Testing.
Adding tests to existing code allows you to capture what you’re learning
about how the code works and unlocks your knowledge such that other
developers can benefit from your work. As you rename a method or
variable and prune some dead code, you’re actively leaving the code better
than you found it.
Some developers insist they need to write copious comments for those who
consume their services. While these comments are less smelly than those
mentioned earlier, they aren’t your only option. Again, tests make for a
more resilient documentation mechanism. Utilizing Consumer-Driven
Contracts allows you to convey what your service does while also giving
you the confidence to iterate as necessary. As long as you haven’t violated
the contract, you can evolve your code freed from the worry that you might
inadvertently break a downstream system. Consumers gain confidence in
your services, as they have a set of tests they can execute that simulates the
expected behavior of your code. They can modify their code without fear of
introducing a new defect.
Consumer-Driven Contracts are a vital part of reliable and resilient
software. Many languages and frameworks have projects you can (and
should!) leverage with your applications. From Spring Cloud Contract to
Pact versions for nearly every platform, you have options.

Avoid Clever Code


Software is hard and the domains you work in are complex. But not all
complexity is equal. In his widely cited No Silver Bullet essay, Fred Brooks
makes the distinction between accidental and essential complexity. In short,
essential complexity is inherent in software, from the nuance of the
business rules, to communicating with your team to the ever changing
nature of a codebase. There is nothing you can do to remove this
complexity from software; it comes with the paycheck. On the other hand,
accidental complexity are ways developers make things harder than they
have to be from noisy technology to heavyweight tooling.
Software is not immune from the proverbial snake oil. Many companies
attempt to sell products or processes that will revolutionize software
delivery. It pays to be skeptical. There are opportunities for improvement of
course, but the scientific method reminds us that extraordinary claims
require extraordinary evidence. You should be vigilant about removing
accidental complexity wherever possible. See Chapter X Working With
Heritage Code for a more detailed discussion.
Languages and frameworks often have error prone forms. For example, take
a look at the Java code below. Can you spot the problem?6 In Java, brackets
are technically optional on a single statement if block. Now, some
developers might argue in favor of omitting the brackets as it is less verbose
and the (essentially) empty lines are a waste of vertical space. But what
happens when another developer adds a second statement? Will they
remember to add the brackets, or will they be seduced by the code indent?

----
if (condition)
doFoo();
doBar();
----
Avoid the error prone forms in your toolchain of choice. Just because you
clearly understand it does not guarantee that the information is widely
distributed across your team. Don’t be afraid to update (or establish) coding
standards to cover these cases. There are a number of static analysis tools
you can add to your deployment pipeline to keep you and your team from
inadvertently introducing these types of problems. Take advantage of them.

Code Reviews
Code reviews can vary from asking a colleague for feedback on a method
all the way to hours-long walkthroughs with several developers. Regardless
of the specific implementation details, code reviews are an excellent way to
learn, share experience, and socialize knowledge. More eyes on code is a
good thing and part of the reason some organizations use pair programming.
Whether formal or not, there are certain practices that can improve your
code review practice. First and foremost, don’t be snarky. Avoid sarcasm.
Asking for feedback can be very stressful for people, and many people take
criticism personally. How you share your comments is critical. Be
empathetic to your teammate. While it may be tempting to use a code
review as an opportunity to drop some esoteric bit of trivia on your team,
the goal is to improve the code, not exhibit your technical expertise.

NOTE
“…the only way to make something great is to recognize that it might not be great yet.
Your goal is to find the best solution, not to measure your personal self-worth by it.” —
Jonas Downey

Focus your attention on the most important things. While style points
matter, your effort is better spent lower down on the Code Review Pyramid.
You can (and should) automate formatting and style related issues; let a
computer handle those. Your time and effort should be spent on the things
computers can’t detect for you. Are method and variable names clear and
concise? Is the code readable? Is there duplication? Does the code have the
proper logging, tracing and metrics? Are interfaces consistent with the rest
of the code? Did the developer use any error prone forms?

It is Hard to be Criticized
Developers often invest a lot of themselves into their work, so sometimes it
is hard not to take feedback personally. Code reviews are not an opportunity
to embarrass someone because they didn’t know about some new language
feature or didn’t immediately see a simpler way to solve a problem. No one
is perfect, everyone makes mistakes. Code reviews are about building better
applications and are about the code, not the coder. Don’t get personal in a
code review. Be humble and ask helpful questions. Critiques are more
digestible when they are sandwiched by compliments, so be sure to point
out the good things too.
Share your experiences. Personal stories carry immense weight and diffuse
people’s natural resistance to change. Offer assistance with things you’ve
encountered on previous projects. Be careful with blanket proclamations.
Make sure you have all the details before you pronounce something won’t
work, as you may be missing a key bit of context. Is there some background
you don’t have? Perhaps there are some constraints you aren’t aware of.
Stick to the code.

NOTE
“Every single one of us is doing the absolute best we can given our state of
consciousness.” —Deepak Chopra

If something in someone’s code concerns you, don’t be afraid to talk to the


developer directly. People can be very defensive, especially in group
situations. A quick one-on-one discussion could be the answer. Don’t
ambush a teammate, no one wins in those interactions. Remember, reviews
are a chance to learn, an opportunity to teach.
Avoid the Checkbox Code Review
In some instances code reviews are little more than a checkbox in the
source code management system. Some organizations require all code to be
reviewed before it is merged into the mainline. It may be an admirable goal,
but more often than not it is little more than one developer asking another to
“review” the code which usually results in them checking the box for their
compatriot. Even though it comes from a good intention, such reviews
defeat the purpose.
Some organizations use pull requests as a way of ensuring code quality.
While they can be more comfortable than spending the afternoon on a video
call arguing over code, pull requests aren’t always conducive to building
team cohesion and trust. Some developers use PRs as an opportunity to nit
pick, start a turf battle or reignite a previously closed issue. Pull requests are
also vulnerable to the LFTM (looks fine to me) response which may be fine
in some cases but falls victim to the previously mentioned checkbox review.
Code reviews focus on the code. Sometimes the model is wrong, or you
choose the wrong abstraction. Design decisions are critical, something
covered in Chapter X UI Design 101.
Text based comments also lack the tone and body language of face-to-face
conversations. You may not intend a comment to come across in an acidic
or biting way, but it may be taken as such by the person on the other end of
the request. Don’t be surprised if some of your teammates, especially those
with less experience, dread a PR. Once again, practice empathy. Ask
yourself how you’d like to be treated and act accordingly. Senior staff
should lead by example.

Fostering Trust
If you shouldn’t do checkboxes or LFTM reviews, what should you do?
Regardless of your code review process, don’t lose sight of the purpose of
code reviews. You should be sharing experiences, learning and growing as a
team while avoiding problematic practices. Code reviews should foster
collective code ownership and foster trust amongst the team. Promoting a
bug of the week or just taking the time to share something you ran into can
be incredibly powerful.
It should go without saying, but treat your teammates with respect. Be kind,
do what’s right, do what works. Don’t be afraid to take a moment to review
your approach and ask if there might be a better way. Whether you’re
following an agile development methodology or not, you should adapt and
adjust on a regular basis. If something isn’t working, change it!

Apply the Golden Rule to Software


As we said earlier, apply the <<golden-rule>> to your code. Think about
the developer who will follow in your footsteps. And again, that developer
may in fact be you!7 What would your future self like to see in the
codebase? What would make life better for the next developer? Code is
ultimately a communication mechanism. Yes, it is a way to instruct the
computer to perform some task, but that pales in importance to its ability to
speak to other developers. Write code that is meant to be read. Optimize for
the human, not the compiler.8

Wrapping Up
Developers write code, it is part and parcel of the job. Avoiding error prone
forms and overly clever code can be the difference between a codebase
that’s a pleasure to work on and one that developers avoid like the plague.
From code reviews to analysis tools, there are many ways to help you write
better code. Favor writing tests over copious comments. When critiquing
code, be empathetic. Never forget, code should be written to be read by
humans; adhering to that principle goes a long way towards ensuring you
write code others will stamp with the elusive “good” label!

Additional Resources
Portrait of a Noob
Favor composition over inheritance
The Mythical Man-Month: Essays on Software Engineering
An Appropriate Use of Metrics
Simple Made Easy

1 Like on the freeway when people notice the state trooper in the median and everyone slows
down
2 Though with modern monitor sizes you may want to stick to a single page.

3 Take the time it takes, so it takes less time.

4 You can often tell the experience level of a developer by their use (or avoidance) of code
comments.
5 Dogmatism, whether in software or life, is rarely the right path. Favor pragmatism.

6 One of your authors, who shall remain nameless in this instance, spent the better part of two
weeks debugging that code block.
7 Every developer has stared at some code wondering what idiot wrote this only to slowly
realize they actually were the “idiot” that wrote said code.
8 With the exception of certain, very specialized programming examples. You’ll know it when
you encounter it.

OceanofPDF.com

You might also like