Computer programming is a particular kind of problem solving.
Speaking generally, the essence of a problem is that we want to
OO programming describes situations by objects; so in the OO context it suffices to talk about objects.
OO programming describes problem solutions by methods. From the problem-solving perspective the purpose of a method is to produce new objects from existing ones or to query or modify existing objects.
We solve problems in steps, i.e., by solving a number of lower-level sub-problems in a certain order.
A method of a class in an OO program describes such a succession of sub-problem solutions.
Inspection of existing real-world programs shows us that methods are very often a conglomeration of problem solutions on several levels of detail. They do not just describe the solution of the actual, primary problem, but also the solutions of lower-level sub-problems in a way which makes it hard to understand all these and to modify and maintain or to completely replace particular of these partial solutions.
We conclude that
can be achieved only if we avoid this conglomeration of problem solutions belonging to several levels of detail. This is what we call
Object-Oriented Problem Separation (OOPS).
It centers around the idea that
in OO programming, every method should solve only one problem,
every problem solution should be described in a separate method of its own,
and in particular: clearly identifiable sub-problems should be described in separate methods.
Individual methods will become much smaller this way and (hopefully) easier to understand and to modify. Our object classes will consist of
Many Irreducible Mini-Methods (MIMM).
Decomposition of programs into many small methods is a common programming idiom in object-oriented languages. The above considerations show how this decomposition can be motivated in terms of "object-oriented problem separation". But OOPS:MIMM raises a number of obvious questions:
A) Doesn't OOPS:MIMM just transform the problem of bloated and conglomerated methods into a problem of bloated and conglomerated classes?
In Lava we have several ways to avoid this, fortunately:
In many cases it will suffice to hide those "lower-level detail methods" in the implementation of the respective class. Since Lava provides complete separation of class interfaces and class implementations, they are concealed then from the eyes of the interface users and cannot confuse them any longer.
In other cases it may be justified to associate sub-problem solutions with completely separate object classes in order to adequately deal with these sub-problems. In some of these cases it may be natural to utilize Lava's support of nested declarations and to nest the corresponding class declarations within the primary class (interface) declaration.
In a third group of cases it may be more natural to utilize Lava's support of multiple inheritance and to provide specific base classes of the primary class for dealing with lower-level sub-problems. These base classes would be viewed as "mix ins" or "ingredients" of the primary class.
B) Isn't it too cumbersome to create and manage many mini-methods?
In cases 2 and 3 above, where it is justified to provide separate classes associated with lower-level sub-problems, the programmer will accept the additional effort, since he/she is rewarded by an obviously improved since more transparent program structure.
The crucial issue with case 1 is that you have to jump from your executable code to a different place in order to declare a new method, and then back to the place in your executable code where you wanted to insert a reference to the new method. In LavaPE this nuisance is largely mitigated
by LavaPE's consequent support of one-click navigation between declarations, implementations and references,
by two special shortcut buttons on the toolbar at the top of exec windows. They are visible if you select the <func> placeholder of a method invocation and allow you to create a new private or exported method (in the implementation or in the interface, respectively) of the call objects (static) class and to insert a reference to the new function for <func>, without the necessity to first find and jump to the declaration or implementation of that class.
If you then want to declare also some input or output parameters for the new method, a single click on LavaPE's "Go to declaration" button brings you to the method's declaration where you will add the desired parameters, and another click on the "Return to reference" button will bring you back to the method reference in your executable code.
C) What are "clearly identifiable sub-problems"?
This is largely a matter of taste and context. Sub-problems will always be "smaller" in some sense or other. There are some general considerations, nevertheless, that may suggest to factor out certain aspects of a program explicitly as sub-problems and to solve them in separate methods:
Object classes are problem classes: If a code passage within a method of a class C isn't actually related to objects of class C but to objects of some completely unrelated class or to objects of a sub- or super-class of C then you should consider to transform the code passage into a method of that other class.
Sub-objects raise sub-problems: Construction of sub-objects (= initialization of member variables) should, as a rule, be factored out. It will mostly be better to initialize member variables on a separate, lower level of detail. This is even enforced in Lava: Within an initializer of a Lava class, a value must be assigned to every non-optional member variable, and you cannot pass an object as a method parameter or call a method of the object before the object has been completely initialized.
This "single-shot" initialization within the initializers of a class is surely one of the most restrictive (and experimental) features of Lava which forces the programmer to construct complex objects (for instance tree structures) in a much more disciplined way in a single pass, but, together with LavaPE's rigorous initialization checks for local variables, it promises to prevent most of the very frequent "missing initialization" errors that would cause hard to analyze crashes in other programming languages. (In some cases you need a way to collectively or recursively initialize several objects that point to each other. This is accomplished by the initialize clause of the declare construct.
Note the distinction between variable "state objects" in Lava that can be modified at any time, and immutable "value objects" that cannot be changed any more after they have left the
new expression. Single-shot initialization applies also to state objects, and it wouldn't comply with the spirit of Lava if you would abuse state objects only to circumvent the single-shot initialization discipline.
Case distinctions may induce sub-problem distinctions: Deeply nested case distinctions as well as long and complex branches will often impede program comprehension to an unacceptable degree. Both should be avoided by shifting long branches and nested distinctions into separate methods.
Big tasks may call for a subdivision into major "milestones": The subdivision may be desirable for various reasons and will often be somewhat arbitrary. It may, e.g., be motivated by the assignment of distinct work items to different people, and/or by arguments concerning the relative stability of certain desirable intermediate goals, or just by clarity/transparency/comprehensibility arguments.
The milestones correspond to input and output parameters of specific methods which describe how we can get from the initial state or from one or several milestones to one or several dependent milestones.
If you exploit all these possibilities of method / sub-problem factoring you will arrive at a class structure with mini-methods that are in this sense irreducible:
MIMM = Many Irreducible Mini-Methods.
The MIMM notion doesn't have a strictly formal definition but shall encourage the programmers to exploit all above-mentioned reduction possibilities to remove all lower-level details from all methods until each of them deals only with a single level of detail and is of minimum complexity. |
Some more general recommendations may help to further improve the comprehensibility of programs:
In any case, you should create a separate new method for some code passage only if you can find a succinct, suggestive, unmistakable name for it, or else it will be of limited value only.
Don't hesitate to use long, composite names, if necessary to achieve this goal.
Use comments in method declarations to explain the essence of the methods.
Use Lava preconditions, postconditions and invariants ("Design By Contract") to characterize the semantics and the implementation restrictions of methods more formally.
D) Isn't it too inefficient to invoke many mini-methods instead of using inline code?
Lava is an interpreted programming language and optimization isn't one of our primary concerns at present. But principally we think that the overhead of mini-method invocations could be avoided in most cases by automatic inlining and automatic recognition of tail recursion by the Lava interpreter (or a future Lava compiler) which would then be replaced by an internal loop execution automatically.
Speaking of loops: The semantics of traditional loops in programming languages is heterogeneous: Partly they are an optimized way to express tail recursion, partly their semantics would be expressed in mathematical terms by quantifiers ranging over finite sets. In contrast to this, Lava prefers to abandon traditional loops, to explicitly provide quantifiers ranging over finite sets, and to use recursive functions where appropriate. This is another "built-in" measure in Lava, beyond the above-mentioned features, to promote the OOPS:MIMM programming discipline.
See also:
Spaghetti-like, tortellini-like and fractal program structures.