The Forkless Philosopher
KISS - Keep It Small and Simple
General Principle
The general principle behind KISS is that any one functional unit
should only do one job (but that job it should do well). A good
example is the suit of little shell utilities well known from Linux
and other **ixes: grep
, awk
and all the
others.
The philosophy behind them is quite simple: every utility concentrates
on doing one job well. So grep
is very good at finding
lines matching a regex pattern in a text file; it is not so good at
displaying just parts of those lines, say the second word. This,
however, is something that awk
is quite good at, so all
you have to do is pipe the result of grep
into
awk
for formatting.
This is a very modular concept und therefore very lightweight. If
you don't need to format the output and just start grep
the memory footprint is lighter than with grep
and
awk
combined. If, instead, you would have just one
utility that does both (call it prettygrep
), the memory
footprint would always be large, because even if you wouldn't
want to format the results, prettygrep
would always be
started with the functionality to do so included.
The principle of doing one job well is also called the "Single
Responsibility Principle", especially when applied to class and
method design. To quote Michael C. Feathers: "The Single
Responsibility Principle tells us that classes should have a single
resposibility. If that's the case, it should be easy enough to write
it down in a single sentence" (Working Effectively With Legacy
Code, Prentice Hall, 2010, p. 260).
Small and Simple Methods
Keeping methods simple usually means not to combine behaviour.
Think of a method that checks stock prices on the internet.
You would do this by querying a webpage that provides free stock
price information from a webserver and then perform some operations
on that webpage (e.g. regular expressions to extract the stock price).
Now, if you do this in one method you can not test this method with a
unit test if, for whatever reasons, the remote server is not available.
If, instead, you break up the job into two parts ("query
webpage", "parse webpage") and put each part into its
own method, you can at least unit test the "parse webpage"
part (provided you keep a sample webpage for testing).
Lets look at some code for the example above. Instead of having just
one does-it-all method
double getStockPrice(string& stock, IWebConnection& con)
{ ... }
you would have two more specialized methods
string getWebPage(string& stock, IWebConnection& con)
{ ... }
and double extractStockPriceFrom(string&
webpage) { ... }
which you would call consecutively:
[...]
When applied to methods, this principle is sometimes called
"separation of concerns" and it is easy to demonstrate
that with the example above:
string page = getWebPage("MSI", mInetConn);
double price = extractStockPriceFrom(page);
[...]
double extractStockPriceFrom(string webpage) { ... }
does not have to be concerned about how the webpage is
fetched, all it has to do is parse it once it has been fetched.
But keeping methods small and simple does also mean avoiding methods
whose implementation takes hundreds of lines of code. Try to break
them down using the refactoring "extract method" until
they are short enough to fit in their entirety into the window of
your favourite editor in a standard setting.
Small and Simple Classes
What is good for methods is good for classes as well. Keep them
simple, concentrating on just one responsibility per class. That
means that instead of cluttering a class with all sorts of methods
that are somehow related to the problem, you group methods of
related functionality into their own classes.
Take, for example, an email-handling class CEmail
.
That class might contain two methods encodeBase64()
and decodeBase64()
because emails can contain Base64
encoded content.
But does it need to? The Single Responsibility Principle in this
case would mean that instead of being members of CEmail
,
encodeBase64()
and decodeBase64()
should
rather be public methods of their own class CBase64
which can then be used by CEmail
.
Such modularity makes for far better testability since now you can
write explicit unit tests for CBase64
which otherwise
might not have been possible: why should CEmail
expose
the two Base64 related methods as public?