Software development is hard. The amount of information you need to
know to be effective in any area of software engineering is staggering.
And the amount of new information coming down the pipe can be
overwhelming. Whether you're doing web services programming in the cloud
or embedded software programming for micro-controllers, you should know
at least one programming language well, and probably more than one to be
really effective. There will be some set of libraries and frameworks to know, and the more you know, the better choices you'll be able to make regarding which ones to use for a given design.
You
should know a good software development process, likely something agile.
Then there's your development environment: the OS, an IDE or text
editor, compilers, build automation, bug tracking, version control, unit
testing, etc. You should have deep knowledge of all of these things.
Depending on your application domain, you'll need to know about digital
signal processing, power management, statistics, probability, user
interfaces, user experience, networking, databases, control systems,
discrete mathematics, graph theory, and the list goes on. Believe me,
that's only a very small, partial list, and don't forget the actual
domain-specific knowledge for the field in which you're writing
software.
The set of knowledge that a software engineer
uses is vast and unique to each programmer, and if you're keeping up on
personal development, you're adding new layers to it all the time.
However, I've found that there's a short list of development practices
that stays fairly constant throughout the years, even with new languages
and frameworks and tools appearing every week. This list may be
different for you. Heck, in the future it may be different for me,
although probably not by much. Up to this point in my career these
practices have served me quite well. They're the concepts I think about
every day while I'm designing and programming, and while tools and
frameworks continue to change, these have largely stayed the same. They
are the stalwart knights of my programming days.
DRY: This principle comes from The Pragmatic Programmer,
and it stands for Don't Repeat Yourself. I'm sure I don't have to go
into too much detail here because any programmer worth his salt already
knows it. For anyone that doesn't, stop reading this right now and go
get a copy of The Pragmatic Programmer. Read it. DRY is the
essence of programming. It's deceptively simple to understand in theory,
and wickedly hard to implement in practice. I am constantly thinking up
and trying new ways to achieve the ideals of this principle.
KISS:
Software engineering is hard enough as it is without us
over-complicating our designs and our code. I always aim to Keep It
Simple, Stupid. Another way to keep this idea in mind is to aim to do the simplest thing that could possibly work.
Don't try to dress up a simple solution in a big, frilly frock of
design patterns and UML diagrams that it doesn't need. Do only what is
necessary to solve the problem at hand, and no more. Over-design is a
house of cards, and if you can barely understand it when you're building
it, it will all come crashing down when you're trying to maintain it
six months from now. Do yourself, or the next programmer that has to
look at your code, a favor and write it as cleanly and concisely as possible, and keep it simple. You'll get more done, you're more likely
to get it right, and you're more likely to understand it later.
YAGNI:
Closely related to KISS, YAGNI is the principle of You Aren't Gonna
Need It. That multi-level inheritance hierarchy for implementing
unspecified future features? You aren't gonna need it. That quadruple
redundant, auto-recovery, exponential hold-off protocol for messaging
between micro-controllers? You aren't gonna need it. That SaaS API for
your new meeting software in the cloud? Seriously, you aren't gonna need
it! At least, you don't need it yet. Get the stuff working now that
needs to be working now, and save the stuff that you could need
later for later. Sure, you can think a bit about how you would add
future features if you really think you're going to need them, so you
don't completely paint yourself into a corner from the beginning. But
there's a big difference between planning for future expansion and
actually erecting all of the scaffolding and roughing in the
architecture. Do the former, not the latter.
Occam's Razor:
There are many formulations of this principle, many coming from
philosophers and scientists other than the one for whom it is attributed
to, William of Ockham. The one that I learned and prefer, but cannot
find a reference to is, "in the absence of contradiction, the simplest
explanation is best." I use this principle mostly as a guide in
troubleshooting and debugging, but also in general problem solving
during design. "Best" can mean everything from most correct to most
useful to most desirable. If you're trying to figure out why a build is
broken, it's much more likely that it was due to some recently added
code with a bug in it than a latent bug that happened to surface now.
It's still possible that it could be the latent bug, but I definitely
wouldn't start looking there. I would start with the most recently
checked in code. Occam's Razor is surprisingly versatile, and I find
myself using it constantly.
Amdahl's Law:
Formally, Amdahl's Law states that "the speedup of a program using
multiple processors in parallel computing is limited by the time needed
for the sequential fraction of the program." That's quite a mouthful,
but basically it means that your program can only be as fast as its
slowest component. Another way to think about it in the context of
optimizing a program for performance is that you're going to get the
greatest speedup from the part of the program that takes the longest to
execute. Sounds obvious, right? So don't spend your time optimizing
esoteric features of a program that only rarely execute or processes
that happen in parallel with long-running IO or database operations. If
your program is spending 40% of its time doing something that the user
is waiting for, focus on that instead.
"select" Isn't Broken: Here's another one from The Pragmatic Programmer.
This time the authors relate a story about "a senior engineer [who] was
convinced that the select system call was broken on Solaris." Don't
fall into this trap. Sure, it really looks like you're using that
library function correctly. You've looked over your code dozens of times
and you are positively convinced that it is doing what it's supposed to
do. It must be a bug in the library. No, the compiler is making a
mistake when optimizing this code section. No, there's a bug in the
processor that I have to work around. Stop, just stop already. Do you
really think any of those systems that have seen orders of magnitude
more use and abuse than your code are all that bug-ridden? Take another
look at your own code. The bug is almost certainly there. In a milder
form of this practice, you should always assume the bug you are seeing
is your own fault. Inspect your own code as thoroughly as you can before
trying to blame it on a coworker's code. You might save yourself some
embarrassment.
Don't Let Standards Make You Stupid:
I don't have a link for this one because I haven't seen this idea
wrapped up into a pithy phrase, yet. Instead, I made up my own. The idea
is that while standards are generally a good thing, they should not
preclude you from thinking about the problem at hand and solving it in
the best way possible. Standards can add some uniformity and consistency
to programming and offer known solutions to common problems, but
standards can also go to far. They can become rigid structures that mask
over issues that are unique to particular problems that require their
own creative thinking. Make sure to balance out a hefty set of standards
with an eye for exceptional circumstances where those standards don't
apply. And then use your brain.
Do Not Reinvent the Wheel: A good way to avoid this problem is to watch out for its main symptom - NIH.
If you find yourself implementing basic string processing algorithms or
building your own data structures, you could be reinventing the wheel.
Try spending a few minutes looking around public code repositories or
even your language's standard library to make sure you're not wasting
your time writing code that's already been written hundreds of times
before, and will likely perform better than yours because it's already
been extensively tested and optimized. There is some need for balance
here because if your problem can be solved quickly with a few short
methods or a small class or two, it may take longer to find, adapt, and
verify someone else's code instead of rolling your own. On the other
hand, if the solution comes from a well maintained framework with a good
API and documentation, you can benefit from future updates to the
framework. And now that you know it exists, you can reuse it in other
projects. In any case, try to find the best way to solve your problems
without blindly resorting to writing more code.
Rubber Duck Debugging:
This is a method of debugging where, when you get completely stuck, you
turn to the rubber duck sitting on your desk, and ask it for help. You
have to explain your problem with enough detail that the duck will be
able to understand the problem and offer a solution. Quite often the act
of constructing a well-formulated question will reveal the solution,
all on its own, and that is the point of the exercise. If not, then
perhaps the duck will miraculously tell you the answer after all. I
wouldn't depend on that, though. For more information on this excellent
problem solving method, I'll point you to Jeff Atwood.
Bedtime Debugging:
Here is another practice that hasn't been phrased as such, but is very
commonly recommended in various ways. It could also be called shower
debugging, or brushing-your-teeth debugging, or go-for-a-walk debugging.
The idea is to work on the problem as much as you can. Give it a good
effort, and if you get stuck, tuck it away in the back of your mind and
go do something mindless. You're subconscious will continue mulling over
the problem, and then when you least expect it, the solution will pop
into your head. When I use this technique, I normally don't immediately
turn to doing something mindless when I get stuck. I'll put my current
problem aside and work on something else. Because my mind is otherwise
occupied, those latent solutions normally don't come to me right away.
They turn up as I'm falling asleep at night. This happens so frequently
that I've learned to task switch as soon as I run out of ideas for my
current problem, instead of staring blankly at the computer screen for
hours. It can be frustrating to leave a problem unsolved, but I could
also waste a lot of time fighting for new ideas without making any
progress. I know I'll figure it out when my mind is calm and relaxed
that night, and I'll make more progress the next day.
Leave It Better Than You Found It:
My last software practice comes from many years in the Boy Scouts. My
scoutmaster was an adamant proponent of Robert Baden-Powell's advice to
"leave this world a little better than you found it." In scouts that
meant cleaning up trash when you saw it, leaving campsites in better
shape than when you arrived, and generally being a good caretaker for
mother nature. That lesson has stuck with me in software engineering,
and when I'm working on a piece of code, I try to leave it better than I
found it, too. I take the time to refactor when I'm changing code so
that variable names are more self-explanatory, methods are shorter and
more understandable, and code formatting is more consistent. I don't go
hog-wild, rewriting every file I touch, but if I see something that
looks out of place, I'll take the time to make it a little bit better
for the next programmer.
That brings my list of good software practices up to eleven.
Maybe that's not such a short list, but they are things I use everyday.
I've found them all immensely valuable, and maybe you will, too.
No comments:
Post a Comment