|
This HTML version of Think Perl 6 is provided for convenience, but it is not the best format of the book. You might prefer to read the PDF version.
Chapter 15 Some Final Advice
Everyone knows that debugging is twice as hard as writing
a program in the first place. So if you’re as clever as you
can be when you write it, how will you ever debug it?
— Brian Kernighan, "The Elements of Programming Style".
15.1 Make it Clear, Keep it Simple
Writing a real-life program is not the same thing as learning
the art of programming or learning a new language. Because the goal of this book is to lead you to learn
more advanced concepts or new syntax, I have often been
pushing new ways of doing things. But this does not mean that
you should try to pack your most advanced knowledge into
each of your programs. Quite the contrary. The rule of thumb is “KISS”: keep it simple, stupid. The
KISS engineering principle (originated in the US Navy
around 1960) states that most systems work best if they
are kept simple rather than made complicated; therefore
simplicity should be a key goal in design and unnecessary
complexity should be avoided. This does not mean, however,
that you should write simplistic code.
As an example, if you’re looking for a literal substring
within a string, use the simple index built-in function,
rather than firing the regex engine for that. Similarly,
if you know the position and length of the substring, then
use the substr function. But if you need a more “fuzzy”
match with perhaps some alternatives or a character class, then
a regex is very likely the right tool.
Another related tenet is “YAGNI”: you aren’t gonna need
it. This acronym comes from a school of programming
known as “extreme programming” (XP). Even if you don’t
adhere to all the XP principles, this idea is quite sound
and well-founded: don’t add any functionality until it is
really needed.
Try to make your programs as clear as possible, and
as simple as you can. Use more advanced concepts if
you have to, but don’t do it for the sake of showing how
masterful you are. Don’t try to be clever or, at least,
don’t be too clever. Remember that code is not only used by the compiler, but
is also by humans. Think about them. Think about the person who will have maintain your code.
As some people like to put it: “Always code as if the person
who ends up maintaining your code is a violent psychopath
who knows where you live.” And, if nothing else will convince
you, remember that the person maintaining your code might be
you a year from now. You may not remember then
how that neat trick you used really works. A final quote from Edsger Dijkstra on this subject:
“Simplicity is prerequisite for reliability.”
15.2 Dos and Don’ts
- Don’t repeat yourself (DRY):
- Avoid code duplication.
If you have the same code in different places of your
program, then something is most likely wrong. Maybe
the repeated code should go into a loop or a separate
subroutine, or perhaps even in a module or a library.
Remember that copy and paste is a source of evil.
- Don’t reinvent the wheel:
- Use existing libraries
and modules when you can; it is likely that they have
been thoroughly tested and will work better than the
quick fix you’re about to write. The Perl 6 ecosystem
has a large and growing collection of software modules
(see modules.perl6.org) that you can use in your
programs.
- Use meaningful identifiers
- If your variables,
subroutines, methods, classes, grammars, modules,
and programs have sensible
names that convey clearly what they are or what they do,
then your code will be clearer and might need fewer
comments. Very short names like
$i or $n are
usually fine for loop variables, but pretty much anything else needs
a name that clearly explains what the content or the purpose is.
Names like $array or %hash may have sometimes been
used in some examples of this book to indicate more clearly
the nature of the data structure, but they are not recommended in
real-life programs. If your hash contains a collection of
words, call it %words or %word-list , not
%hash . The % sigil indicates that it is a hash anyway.
- Make useful comments and avoid useless ones
- A
comment like this:
my $count = 1; # assigns 1 to $count
is completely useless. In general, your comments should
explain neither what your code is doing nor even how it is doing
it (this should be obvious if your code is clear), but
rather why you are doing that: perhaps you should refer
to a math theorem, a law of physics, a design decision, or
a business rule.
- Remove dead code and code scaffolding
- Even when
writing new code, you may at some point create variables
that you don’t use in the final version of your code. If so,
remove them; don’t let them distract the attention of your
reader. If you modify an existing program, clean up the place
after you’ve changed it. Remember the boy scout’s rule:
leave the place better and cleaner than you found it.
- Test aggressively
- Nobody can write any piece of
significant software without having a number of initial
bugs. Edsger Dijkstra is quoted as saying: “If
debugging is the process of removing software bugs,
then programming must be the process of putting them in.”
It is unfortunately very true. Even though Dijkstra
also said that “Testing shows the presence, not the absence
of bugs,” testing is an essential part of software
development. Write extensive test plans, use them often, and
update them as the functionality evolves. See Section ??
(p. ??) for some automated testing tools.
- Avoid premature optimization
- In the words of Donald
Knuth: “Premature optimization is the source of all evil (or
at least most of it) in programming.”
- Don’t use magical numbers:
- Consider this:
my $time-left = 31536000;
What is this 31,536,000 number coming out of nowhere?
There’s no way to know just by looking at this line
of code. Compare with this: my $secondsInAYear = 365 * 24 * 60 * 60;
# ...
my $time-left = $secondsInAYear;
Isn’t the second version clearer? Well, to tell the
truth, it would be even better to use a constant in
such a case:
constant SECONDS-PER-YEAR = 365 * 24 * 60 * 60;
- Avoid hardcoded values:
- Hard-coded values are bad.
If you have to use some, define them as variables or constants
at the beginning of your program, and use those variables or
constants instead. Hard-coded file paths are especially bad. If you
have to use some, use some variables with relative paths:
my $base-dir = '/path/to/application/data';
my $input-dir = "$base-dir/INPUT";
my $result-dir = "$base-dir/RESULT";
my $temp-dir = "$base-dir/TEMP";
my $log-dir = "$base-dir/LOG";
At least, if the path must change, you have to change only
the top code line.
- Don’t ignore errors returned by subroutines or built-in
functions:
- Not all return values are useful; for example, we
usually don’t check the return value of a print statement, but
that’s usually fine because we are interested in the side effect,
the fact of printing something out to the screen or to a file,
rather than in the return value. In most other cases, you need
to know if something went wrong in order to take steps to either
recover from the error condition, if possible, or to abort the
program gracefully (e.g., with an informative error message) if
the error is too serious for the program to continue.
- Format your code clearly and consistently
- The compiler
might not care about code indentation, but human readers do.
Your code formatting should help clarify the structure and
control flow of your programs.
- Be nice and have fun.
-
15.3 Use Idioms
Any language has its own “best practice” methods of use.
These are the idioms that experienced programmers use, ways of doing
things that have become preferred over time. These idioms are important.
They protect you from reinventing the wheel. They are also what
experienced users expect to read; they are familiar
and enable you to focus on the overall code design rather than
get bogged down in detailed code concerns. They often formalize
patterns that avoid common mistakes or bugs. Even though Perl 6 is a relatively new language, a number of such
idioms have become honed over time. Here are a few of these
idiomatic constructs 1.
-
Creating a hash from a list of keys and a list of values
- Using slices:
my %hash; %hash{@keys} = @values;
Using the zip operator and a metaoperator with the pair constructor:
my %hash = @keys Z=> @values;
For existence tests, the hash values only need to be true. Here is
a good way to create a hash from a list of keys:
my %exists = @keys X=> True;
Or, better yet, use a set:
my $exists = @keys.Set;
say "exists" if $exists{$object};
- Making mandatory attributes (or subroutine parameters):
-
This is a nice way of making a mandatory attribute in a class:
has $.attr = die "The 'attr' attribute is mandatory";
This code uses the default value mechanism: if a value is supplied,
then the code for the default value does not run. If no value is
supplied, then the code dies with the appropriate error message.
The same mechanism can be used for subroutine parameters.Or you could use the is required trait: > class A { has $.a is required };
> A.new;
The attribute '$!a' is required,
but you did not provide a value for it.
You can even pass a explanatory message: > class A { has $.a is required("We need it") };
> A.new;
The attribute '$!a' is required because We need it,
but you did not provide a value for it.
- Iterating over the subscripts of an array
- The first
solution that comes to mind might be:
for 0 .. @array.end -> $i {...}
That’s fine, but this is probably even better:
for @array.keys -> $i {...}
- Iterating over the subscripts and values of an array
-
The
.kv method, in combination with a pointy block
taking two parameters, allows you to easily iterate over an
array:
for @array.kv -> $i, $value {...}
- Printing the number of items in an array
- Two possible
solutions:
say +@array;
# or:
say @array.elems;
- Do something every third time
- Use the
%%
divisibility operator on the loop variable:
if $i %% 3 {...}
- Do something n times:
- Use the right-open range operator:
for 0 ..^ $n {...}
# or, simpler:
for ^$n {...}
- Split a string into words (splitting on space):
-
A method call without an explicit invocant always uses
the
$_ topical variable as an implicit invocant. Thus,
assuming the string has been topicalized into $_ :
@words = .split(/\s+/);
# or, simpler:
@words = .words;
- An infinite loop
- A loop statement with no parentheses
and no argument loops forever:
while True {...}
# or, more idiomatic:
loop {...}
Of course, the body of the loop statement must have some
kind of flow control statement to exit the loop at some point. - Returning the unique elements of a list
- The unique
method removes duplicates from the input list:
return @array.unique;
Or, if you know that your list is sorted, you can use the
squish function (which removes adjacent duplicates).
- Adding up the items of a list
- Use the reduce
function or the reduction metaoperator:
my $sum = @a.reduce(* + *);
# or, simpler:
my $sum = [+] @a;
# or yet simpler, using the sum built-in:
my $sum = @a.sum;
- Swapping two variables
- Use the
.= mutating
method call with the reverse function:
( $x, $y ) = $y, $x;
# or:
( $x, $y ) .= reverse; # equivalent to: ($x, $y) = ($x, $y).reverse
- Generating random integers between 2 and 6
- Use the
..
range operator and the pick method:
$z = 2 + Int(5.rand);
# or, better:
$z = (2..6).pick;
- Count by steps of 3 in an infinite loop
- Use the
...
sequence operator with the “*” whatever star operator and
a pointy block:
for 3, * + 3 ... * -> $n {...}
# or:
for 3, 6, 9 ... * -> $n {...}
- Loop on a range of values, discounting the range limits:
- Use
the open range operator:
for ($start+1) .. ($end-1) -> $i {...}
# or, better:
for $start ^..^ $end -> $i {...}
15.4 What’s Next?
A book like this one can’t tell you everything about programming,
nor about Perl 6. At this point, you should know how to write a
program to solve an average-difficulty problem, but a lot of work
has been done in the last decades to solve harder problems. So
where should you go from here? Read books about algorithmics, the science of algorithms. Many good
books exist on the subject, but I especially recommend the following
two (you should be aware, though, that they are not easy):
-
Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest,
and Clifford Stein, Introduction to
Algorithms, The MIT Press
- Donald Knuth, The Art of Computer Programming, Addison Wesley
(many volumes, many editions).
Read other books about programming, even if they
target other programming languages or no specific programming
language. They’re likely to have a
different approach on various points; this will offer you a
different perspective and perhaps better comprehension, and
will complement what you have read here.
Read tutorials, articles, blogs, and forums about programming.
Participate when you can. Read the Introduction to
Perl 6 which exists in eight different languages as of
this writing (http://perl6intro.com/). Read the
official Perl 6 documentation (https://docs.perl6.org).
This book has more than a thousand code examples, which is quite
a lot, but may not be sufficient if you really want to learn more.
You should also read code samples written by others. Look for
open source libraries or modules and try to understand what they
do and how they do it. Try to use them. Having said that, I should stress that you can read as many books
as you want about the theory of swimming, but you’ll never know
swimming until you really get around to doing it. The same is true
about learning to program and learning a programming
language. Write new code. Modify existing examples, and see what
happens. Try new things. Go ahead, be bold, dive into the pool and
swim. The bottom line is: you will really learn by doing. Learning the art of programming is great fun. Enjoy it!
|
Are you using one of our books in a class? We'd like to know
about it. Please consider filling out this short survey.
Think DSP
Think Java
Think Bayes
Think Python 2e
Think Stats 2e
Think Complexity
|