Subject: RE: [hackers-il] How to define "modular" code? [rather long]

From: Omer Musaev <[email-masked]>
Date: Sun, 21 Apr 2002 14:16:12 +0200

> -----Original Message-----
> From: guy keren [mailto:[email-masked]]
> Sent: Sunday, April 21, 2002 12:08 PM
> To: [email-masked]'
> Subject: RE: [hackers-il] How to define "modular" code?
> 
> 
> 
> On Sun, 21 Apr 2002, Omer Musaev wrote:
> 
> > > Most experienced hackers can enumerate the principles of 
> OOP or FP or
> > > Patterns P. However, I found modularity to be much harder 
> to define.
> > > According to OmerZ, it is the most important paradigm, but some
> > > beginning hackers cannot understand what it is. (and 
> Googlizing for it
> > > turns out zilch)
> 
> "hackers" don't write modular code. they hack. you probably meant
> "designers" or "programmers"? ;)
> 
> > > What are the concepts behind modular design? Things of 
> top of my head:
> > >
> > > 1. Make sure that there is no duplicate code (or only such in
> > > appropriate
> > > places)
> >
> > Consequence: Templatize.
> 
> that's _one_ way to avoid duplicate code. there are others. 
> thus, its not
> a 'consequence:'. its 'one method:'. don't let "C++ generic 
> programming"
> hide out all other methods ;)

Correction accepted:)

> 
> > > 2. Make sure everything appropriate is instantizable.
> >
> > I did not understand that statement. What did you intend to say?
> 
> i think he meant "don't use globals. each object should be 
> stored fully in
> some data structure that can be instantiated during runtime, 
> so you can
> create as many different objects of that kind, as you need".
> i also see it as a "no for singletons - cause there never are real
> singletons, as the program's requirements begin to change".
> 

Ok, I see it. 

> > > 3. Make sure each object (not necessarily inherited) or 
> function has a
> > > well-defined interface with few if any side effects on the
> > > external environment.
> >
> > Consequence: Preffer functors on functions.
> 
> why?

Provided that you have a function with a state, you'd better carry
that state in a closure than to rely on an external storage for that
state. Since in too many cases functions do have a state, functors 
( or closures in LISP, or classes in OO languages ) provide a clean
way to have own environment.

> 
> > > 4. Avoiding Kludges that add extra features or solve a
> > > particular problem.
> > > Sub-cases should be generalized to keep the code modular.
> >
> > This is dangerous statement. We had been stuck lately into a project
> > that was heavily generalized and as a consequence of that failed
> > to perform as required.
> 
> its not 'a dangerous statement'. what you are saying is that 
> modular code
> comes at the expense of performance. the question was about 
> modular code.
> you have other considerations when designing/writing code. when you
> actually go design some system - then you consider the tradeoffs and
> decide.

Note that I said "dangerous", not "bad practice". 

The reason for a danger is that while code comes to solve a 
problem, modularity is a property, and not a goal. 

The danger is that while setting that property to be one of goals, 
other goals get shadowed out.

> 
> > > 5. Trying to keep functions non-monolithic and to split them
> > > into several
> > > logical routines.
> >
> > This is dangerous statement too. One can find himself in "too many
> > layers of abstraction" hell quite easy.
> 
> i can second that. and for some reason, this seems to be a 
> common sickness
> amongst people who are "too deep into OO" ;)  turning every 
> little thing
> into an object, and having a class on top of a class on top of a...
> 
> > > What else can you think of?
> >
> > Policy based design[1],[4]. Contract based design[2]. Generic
> > programming[1],[3]
> > Strong typing. Platform independent code.
> 
> those were the buzzwords. can we have their interpretation 
> now, and how
> they are related to modularity (no, i won't go looking into those
> resources unles i have some intensive to do so, in the saying 
> of at least
> one sentence explaining what 'policy based design' is.

I'll try to give a paragraph by a buzzword. However, there are more
resource on the Net, so I will not be over verbose.

1.  Policy based design 

This buzzword describes a methodology of breaking functionality down
into "policies", which describe a way to complete that functionality.

For example: 

Consider logging facility. If used in single threaded environment, 
no locking is necessary. On the other hand, when used in 
multi-threaded environment, one should take care with concurrency
issues. By defining a set of behaviors and passing different sets
on different occasions, one can outsource the locking problem to
a locking policy.

Code example (crude and extra-vocal)

class Default_Locking_Policy 
{
public:
  typedef int Lock ;
}; 

class RAII_Locking_Policy 
{
public:
  class Lock {
    public: 
      // Real locking should go here
      Lock() { cout << "Acquring lock" << endl ; } 
     ~Lock() { cout << "Releasing lock" << endl ; }
   } ;
} ;

// Pay attention!! - Log is a functor, and as such its use
// is simpler than of a class, but more robust than of a 
// function!!
template <class Locking_Policy = Default_Locking_Policy >
class Log
{
public:
     void operator() ( const string& entry )
     {
         Locking_Policy::Lock guard ;
         cout << "Logging: " << entry << endl ;
     } 
} ;

int
main( int argc, char* argv[] )
{
   Log<> single_threaded_logger ;
   single_threaded_logger( "Entry 1" ) ;
  
   Log< RAII_Locking_Policy > mt_safe_logger ;
   mt_safe_logger( "Entry 2" ) ;
}

Here, we have locking out sourced as a policy. The example may
sound a bit silly - the only thing that is out-sourced is definition
of Lock. This is indeed very compact definition of policy. The point 
of policy is however, not passing definitions of classes, but grouping
together families of definitions. Some definitions rely on other, 
while some other definitions conflict one with another. By setting 
a policy, all those details are hidden in one package.

2. Contract Based design:
> as for contract-based design, if i remember it correctly, its 
> about having
> a very strict interface between caller and callee, regarding 
> what values
> may or may not be in different parameters, and the status of data
> structures used. 

Yes, exactly that. 
>what does this got to do with modularity,  any more then
> "write good code" has to do with modularity?

A lot. The quintessence of a modular code is its ability to 
provide functionality in-place without paying attention to 
its inner mechanics. 

Contract based design puts the functionality to be a function
of an interface - in other words, if you complete your part of 
a contract, you have that functionality. By out-sourcing the 
run-time requirements to a "contract" ( interface ), one can 
use a module as a "black box".

3. Generic programming.

Generic programming is about the same issue - separation of 
data from algorithms. It can be seen as a subset of a 
Cartesian product of both policy based design with a contract
based design.

Note that generic programming is not a C++ buzzword, as one can
state, but rather a old approach of "making abstraction with data
and functions", that came from LISP community.

4. Strong typing.

Everyone knows what strong typing is and what it is not.
However, by using "types" as a way to tell compiler to do
the hard work of making sure that thing will work together
one can fortify abovementioned design approaches with 
compile time checks. 

> 
> -- 
> guy
> 
> "For world domination - press 1,
>  or dial 0, and please hold, for the creator." -- nob o. dy
>
-- 
Omer Mussaev
Software Developer
Mercury Interactive / Topaz Prism R&D
Application Team

---

From: Oleg Goldshmidt <[email-masked]>
Date: 21 Apr 2002 15:04:44 +0300

> problem, modularity is a property, and not a goal. 

The implied goal is maintainability, I guess, and modularity is a way
towards that goal. Performance issues (that was the "danger" of too
much modularity alluded to by Omer earlier) are typically best
addressed by identifying which "modules" (e.g. functions) are the
bottlenecks, and optimizing these parts. Modularity helps here.
Sometimes it is the function calls themselves (or other over-modular
designs, such as those that plague C++/OOP programs all too often, as
mentioned) are the problem. In these cases, they can more often than
not be inlined to avoid call overhead without sacrificing modularity.

> > > > into several logical routines.
> > >

The guiding principle is usually that a function should do one thing well.

> > > > What else can you think of?

An issue that is often overlooked is dependencies. Code-level
dependencies, compile-time dependencies, link-time dependencies, etc.
In practical terms, this means thinking of physical organization of
your program, dividing it into files, thinking what should go into
source files and what into headers, hiding implementations, using
forward declarations rather than including headers where sufficient,
using techniques such as opaque pointers etc, etc.

Note that this is quite separate from abstracting actions into
functions, which was the bulk of the discussion in this thread. 

-- 
Oleg Goldshmidt | [email-masked] 
[Lisp] is the only computer language that is beautiful. 
                      - Neal Stephenson

---

From: Shlomi Fish <[email-masked]>
Date: Sun, 21 Apr 2002 16:39:50 +0300 (IDT)

> using techniques such as opaque pointers etc, etc.

Forwards declarations?

You mean something like:

extren int myfunc(int myarg, char * mystring);

.
.
.
	myfunc(a, str);
.
.
.

Using them is something I try to avoid because if I change the definition
in the other module (to say int (int, char *, int)), then I may still call
this function with incorrect parameters.

From that reason it is best for the definition module to include the
header file, so the function will be checked against its prototype.

With C++ where functions can be overloaded it's a different matter
altogether, and, unfortunately, I have little experience in this area.

Regards,

	Shlomi Fish

I'm a bit tired of this sig. I remember having written beuatiful code in
many languages beside LISP and Scheme: Haskell, Perl, C, Matlab, to name
the prominent one. And LISP can get a bit messy when using ugly macro
tricks and evals all over the place.

>
> To unsubscribe from this group, send an email to:
> [email-masked]
>
>
>
> Your use of Yahoo! Groups is subject to http://docs.yahoo.com/info/terms/
>
>

----------------------------------------------------------------------
Shlomi Fish        [email-masked]
Home Page:         http://t2.technion.ac.il/~shlomif/
Home E-mail:       [email-masked]

"Let's suppose you have a table with 2^n cups..."
"Wait a second - is n a natural number?"

---

From: Oleg Goldshmidt <[email-masked]>
Date: 21 Apr 2002 17:21:26 +0300

> Forwards declarations?

I mean that if you use an entity "in name only", as opposed to "in
size", then you do not really need the definition of the entity.

E.g. you have an implementation of an object that is represented by

struct bar
{
   ...
};

with public interface that looks like

int foo(struct bar *xyzzy)
{
    ...
}

The natural (?) tendency of many C programmers is to put the
definition of struct bar and the prototype of foo() in a header file,
and the implementation of foo() and other stuff in the C file.

Instead, you can have just 

/* bar.h */

#ifndef BAR_H
#define BAR_H

struct bar; /* forward declaration */

int foo(struct bar *xyzzy);

#endif

in the header, and put the actual definition of struct bar in the C
file. Of course, to do that, you will need to provide a complete
enough interface to struct bar so that no client will need to know
explicitly what bar consists of (i.e. no client will need to use
bar "in size").

Let's take it one step further. Suppose that you have an object (just
for the hell of it, switch from C to C++) that "holds" struct
bar. Question: do you really need to include bar.h in the header file,
like this:

extern "C" 
{
#include "bar.h"
}

class fubar
{
    struct bar* _bar;
    ...
};

or not? Answer: it is enough to use

struct bar; /* forward declaration */

class fubar
{
    struct bar* _bar;
    ...
};

- you can include bar.h in the implementation C++ file where you will
use foo(). Note that the actual struct definition is not needed even
there.

Does this matter? In big projects - yes, because you have removed a
compile-time dependency (well, the team has to be systematic about
this in order to achieve the desired effect). In small projects -
probably not very much, but in small projects modularity matters less
in general.

I leave you as an exercise to list all the long-term benefits this
affords the maintainer.

(let ((subj off-topic))
  (quote (list 

> I'm a bit tired of this sig. 

And I have no time to change it. I'll try. Bear with me.

> I remember having written beuatiful code 

The quote is about a beautiful language, not beatiful code.

> And LISP can get a bit messy when using ugly macro tricks

Huh? Use macros to make code beatiful, not ugly. Can it be that it
is a C programmer speaking, C macros being such a blunt and
fundamentally broken (side effects!) tool?

)))

---

From: Shlomi Fish <[email-masked]>
Date: Mon, 22 Apr 2002 13:34:19 +0300 (IDT)

> The quote is about a beautiful language, not beatiful code.

So how do you a define a beautiful language? If:

1. It is possible to write a beautiful code in a non-beautiful language.
2. It is possible to write non-beautiful code in a beautiful language.

So, I think declaring languages to be beautiful is a meaningless
statement. And, AFAIC, there are other languages beside LISP which are
beautiful.

> fundamentally broken (side effects!) tool?

LISP Macros can also have side-effects, because you can put everything in
a macro. However, I've seen a lot of LISP code that used various eval's, `
and , and @ and all kind of other stuff that made it horrid and very hard
to understand. And naturally, LISP does not preclude one from writing
other types of bad code.

Regards,

	Shlomi Fish

Just to confuse the interpreter: ((((