In an article on functional programming
this Python code is declared as bad:
def respond(channel, msg):
if channel.isValid() and msg.body() == "next":
return channel.send("A2-B3")
else:
return channel.send("bye")
(I've corrected the obvious non-Pythonness of the original code, since
that wasn't the point.) The author is unhappy that he doesn't know
the value (i.e. implementation) of channel.isValid, msg.body
and channel.send. If he knew the type of channel and msg
he would know these things. And realistically these types are going
to be relatively static, but you can't tell from a localized snippet
of code. (Note that type inference doesn't help here at all, since
with inference the types will be just as mysterious from simple code
reading.)
The problem here is that the code has a language of its own. It talks
about things like "channels" and "messages" without specifying what
exactly it means. These terms probably are all over the code. And
once you become familiar with the code base it is probably obvious
what they mean, and tedious to constantly describe them.
This is a common situation in frameworks, though in this case the
"framework" might simply be the rest of the code in the application
outside of the module or function you are looking at.
It's often a very good tradeoff to ask the programmer new to your code
to spend some time familiarizing him or herself with the lay of the
land -- the basic ideas that are pervasive in your application, the
objects that form the core of it, the words you use to describe its
process.
Generally in Python (and elsewhere) we lack good conventions for doing
this. Extracted documentation (ala Javadoc) is horrible for this;
doctests aren't much better. Interfaces are generally quite good, if
you separate the important interfaces from the incidental ones.
Narratives and code maps are usually the best. But they often not
present, or incomplete, or hidden, or not trustworthy. (Even when
documentation is true it may still not be trustworthy.)
We (meaning "we programmers") should try to do better here. We should
also be careful about the tradeoff -- adding new fundamental ideas to
your code can be a great way to increase the expressiveness and
cohesiveness of the code. But it also has a very real cost; it should
not be done lightly. It is often better to repeat oneself than to
introduce a new metaprogramming construct, a new object type, a subtle
polymorphism, or use higher-level functions. I know, it feels cool to
do all these things. It makes higher level languages seem justified
-- if you are writing code that looks like C, why not write C? It
lets you keep from repeating yourself. But it's often not worth it.
It's often better to repeat yourself, to write dumb imperative code,
to use short statements instead of long expressions, to add extra
names to your code, to use lots of vertical space, to use a for loop,
to use fundamental types over custom types, to avoid subclassing, to
reimplement instead of generalize, to calculate eagerly instead of
lazily, to bind tightly instead of loosely, to use functions over
methods... all the things that are so often called bad are usually
good, because they are usually easy. People overstate their case on
these matters because it is hard to keep other people from doing the
easy thing when it is the wrong thing. But programming-in-the-small
(one function, one line, one simple routine) is best done in the easy
way, even if it isn't as exciting.