Let's get on with the programming-hints-list, shall we?
5. Readable code, // Commenting
What I'm
about to say, is probably NOT what you learned at school. School often tells to
throw a whole book of comments in your code. So somebody else can pick it up
more easily… If he reads that whole book, that is. Hence, there is often more
comment than actual code. And who's reading that? Exactly. Nobody. You're not
reading it, because you already know what that code does. That other guy isn't
reading it either, because nobody likes to read a whole book. Especially not if
its code-comment. If code cannot be grasped within a short amount of time, programmers tend to call it junk and start to rewrite it. With their own junk.
I'm not
saying comments are stupid. I'm saying just don't overdo them. In the basis,
your variable and function names should be self-explanatory enough. Captain
obvious, but still we tend to complicate those functions by letting them do
additional stuff the reader wouldn't know. If we call:
BarberCutMyHair(
Prince_Of_Bel_Air_Style )
We don't
expect it to paint my toe nails as well, get outa here. Keep it simple. A
"Cosine" function performs the cosine. Nothing more, nothing less.
And if you're getting a big load of functions, split them into groups or modules. And eventually make helper-functions on top that do multiple things (and clearly explain that - here you
have your comment). Using the right names is worth far more than some comments
to explain afterwards.
//
name : addTwoNumbers
//
params :
A, B (int) the numbers to add
//
Returns :
The sum of A and B (int)
// Description : Add A to B
function addTwoNumbers( const A, B : integer ) : integer;
// Description : Add A to B
function addTwoNumbers( const A, B : integer ) : integer;
var
C : integer;
begin
// Add A to B
C :=
A + B; //
Store in C
// Return C as a result
result
:= C; // C =
result
// End of function
end;
And now
read this.
function addTwoNumbers( const A, B : integer ) : integer;
begin
result
:= A + B;
end;
Your
teacher may not be happy with the second version, but we are smart enough to
understand what's going on right? Get rid of useless comments, and keep them
for grouping your function into sections, or for REAL important notes. So... if
you see comments, you are actually TRIGGERED to read them, because you know something
useful is in there.
Speaking
of Readable code. I hate those auto-completion editors that force the tabs for
you (some do a good job though). What I always do, is adding one or few tabs on
the left side. You know why? So I can forward TEST / TODO / ERROR comments and
code to the far left. If I scroll down quickly though a document, I can
immediately pick out stuff like this, without even reading the code. Thanks to their
ugliness, comment lines like these stand out, and don't get lost between the others:
function addTwoNumbers( const A, B : integer ) : integer;
begin
result
:= A + B;
// TEST. Lets multiply instead baby
result := A * B;
end;
This is the type of screen I'm looking at 99% of my life, so I'd better
make it tidy.
6. Size matters. Small sizes.
Starting
a new project, excited. Code rambles out of your fingers like a MG42
machine-gun. Man, you’re just too cool. Four months later, it’s done, and you
move on to another project. Four years later, some annoying bitch in the office
emails that the Export-to-Excel-button doesn’t work anymore. It says “Access
Violation F00236A”, or something. Lame.
If your
program were to be 400 lines only, I bet you’ll patch it up in no time, doc. If
it’s a 40.000 line program doing a lot more, thing may get a bit more tricky.
Maybe there are 10 screens with “Export” buttons, but of course she forgot to
mention which one, or what options she checked. And if there are 4 files, 6
different classes, and a few thousand lines behind those buttons, hidden deep
inside the code, I’m pretty sure you’re not that excited to debug it, four
years after.
Well,
things like that happen regardless. And all the lessons you learn here about
avoiding stupid comments, using “Try Catch” and error messages, Modular design,
or having Readable code in general will be helpful. Hence, you may even want to
consider your work tools at forehand when this scenario is to be expected. C++
(or Delphi / Java / VB / … any written language) are relative hard to analyse
and fix, in general. PLC ladder diagrams or other tools that let you code in a
more visual, “block-to-block”, way, are usually much easier to figure out.
Eventually even by another person, years after.
It’s one
of the reasons we use IQAN at work for mobile agricultural equipment. I do get
phone-calls –and always on Sundays, when I’m shitting, drunk, or hanging upside
down- with strange problems on eight-year old machines. The track-suspension
doesn’t work, why?! Obviously I don’t remember all the conditions and possible
error situations, so I’ll have to dig in the code. And thanks to the
straight-to-the-point, visual way and live-debugging tools, I only need a few
minutes usually to get familiar with the code again and figure out what’s going
on.
What I’m
trying to say here is, the choice of this tool saved us dozens of hours, money,
and angry clients waiting on a broken machine. As much as I love “normal”
coding, I would never pick C++ or the likes for this type of application.
But, if
you are stuck to a written language, then at least try to keep your code clean
and compact. Less lines = less reading = shorter learning curve = quicker
results. Nice and all, but it’s not that Bill Gates can order his programmers
to downsize the Windows OS to 600 lines, or a 64K program. No, of course not.
But still, you could:
·
Split
one SuperProgram up into smaller task-specific “Apps”
·
Make
reusable modules (see 7) and build these “Apps” on top
·
Don’t
bang all your code into one huge file
·
Move
the bigger / more complicated code into the background (files, modules, …)
·
Where
possible, use easy-to-make (configuration) files or visual ways to define behaviour or data,
instead of hard-written code. Easier to tinger afterwards.
In other
words, if you’re looking for “Export” related code in your program, don’t slap
the reader with other functions, classes, files or global variables that have
nothing to do with it. And if possible, keep the programmer completely out of certain sections. If my graphics look like crap, I'm not digging in actual OpenGL library code either.
On a
more global level, it’s useful to draw boundaries. And be clear about them. Why
did the office lady get that “Access Violation” in all of a sudden? After four
years? Something must have changed apparently. Newer version of Excel? Bloody
Windows10 again? Tried something different than usual? And there you have it:
her computer crashed because she spilled donuts in the CD-ROM drive, so IT gave
her a new station and OpenOffice was installed instead of MS Office. Yep, that
won’t work. Stupid lady. Stupid IT. Or stupid program, for not telling what is
missing? Don't promise rockets if you're making a boat.
7. Reusable code & Modular design
When
making a bigger, multifaceted application(set), chances are you’ll get some
common functionality beneath it. For example, let’s say you work a lot with XML
files. Instead of re-writing a Reader/Writer function in every program, it’s
better to make a “XML library” that can be reused in all your programs. A
library could be just some shared file, or a DLL, statically linked library, or
maybe even an API running on some web-service. Depends a bit on the
circumstances.
Anyhow,
as you also learn with OOP (Object Oriented Programming), you’ll be making
reusable code-parts sooner or later. Classes or functions that are used on
multiple places, and inherited by “superClasses”. After a while, you will also
learn that some of those classes can also be useful for other programs. So you
probably share code-files to begin with. When growing even further, you will
get a whole collection of such “common functions”. Could be a XML library, a
math library, sound, graphics, communication… So, you decide to pack them
together in a DLL, API, Framework, or whatever you’d like to call it.
At
least, that’s what you should be doing. However, as logical as it sounds, it can
still be hard to make truly reusable functions, worth being in their own
library. When you try to rip out your “AwesomeStuff class” and put it in a
separate library (project), chances are it won’t work. Because “AwesomeStuff
class” is depending on a lot of other code or variables in your original
program. So you start moving them over as well, even though you feel those
functions don’t really belong in a separate library. Setting up the same stuff
on two places, too specific, too much ties and principles inherited from its
original host program.
That doesn’t
make good modular code. I’ve seen people trying to create their own
“AwesomeStuff DLL” just because they feel it’s a good thing to do. But when
looking into that DLL, it’s not all that awesome. It’s actually pretty much
bound to a very specific application, and for the sake of easy debugging, he or
she would have been better off leaving that code just there –in its original
host program. It’s like stealing somebodies child, putting it in another house,
and then tell its original parents to raise it.
Still,
you have that urgent feeling you should be moving your more common functions to
a library; modular design, you know. Cool, but do it in the right order. When
restarting Engine22, or when making a machine-display Framework at work, I
started building these modules first, instead of making the final program and
ripping out common functions. Or actually, I started on paper, making up what
would be good candidates to become (stand-alone) modules. There are no strict
rules, but at least some guiders you can use:
·
Group
and separate logically:
o
As
mentioned in Point #5, the name of a module should be self-explanatory as well.
o
“Sound”
does Sound. Not Physics or Aerobics.
o
Then
again check if it’s worth making a module. If there is just a few functions,
giving its own module might be a bit overdone. Don’t end up with fifty DLL’s
for every fart.
o
Draw
on paper. Robust names, not too much dependencies? Can you explain this to a
non-programmer? Bingo.
·
Try
to disconnect your head from one specific Target application (“Tower22”):
o
Use
functions that can be used in multiple scenario’s
o
For
my Machine Display Framework, I thought about harvesters, but also about
cranes, dumptrucks, ships, …
o
But
do not implement scenario-specific features. Provide help-functions they can
use instead. Keep the actual Rocket-booster logic for “Spaceshuttle.exe”. Or
make layer in between for that.
o
So
keep it generic, yet clear.
o
A
function is what it is. Nothing more, nothing less. No hidden bonus-goodies.
o
Develop
and compile your library as much stand-alone as possible
o
Use
(small) test-programs to, well, test it
·
Place
yourself in another developer, using your library
o
The
main purpose is to SIMPLIFY; do hard work via a few easy functions
o
Again,
good naming is key
o
The
less your library exports, the easier to learn
o
10
functions are easier to master than 1000 functions
o
But
again, be careful not to make your functions to much “multi-purpose”. A
“readXMLfile” should just read the file. Not actually trying to parse it and do
stuff. Split up when needed.
o
For
every function, ask yourself if and how the end-user will use it
o
Even
better, let another programmer actually try your library. Filter out the
confusions, bugs, the dos, the don’ts.
In a nutshell,
try to separate common & application-specific parts. A library like OpenGL
offers a big bunch of generic 3D-drawing functions, but doesn’t do any specific
rendering-technique for you. It does not calculate collisions, draw shadows or
make awesome reflections. It just gives you the tools to get there easier.
It’s not
uncommon to make a more specific module on top of more abstract modules though.
The Engine22 “Graphics” module is basically a wrapper that brings all the
OpenGL magic together. So that I don’t have to worry about it anywhere else in
the code. Not a single OpenGL command is written outside that module.
No comments:
Post a Comment