If you have any suggestions or additions on this theme, let me know about it.
I admit that some of Smalltalk/X's code does not follow those guidelines:
some code is very old and the author(s) of the code have matured as we all do.
Bad code is and will be refactored, whenever we encounter it.
#version and #documentation there:
#version methods comment consists of a single
version line - this is created automatically by the source code
control system (RCS or SCCS).
NEVER manually change this string in the browser - ST/X depends on it, in order to be able to extract a classes correct source code from the source repository.
If you report an error (to eXept), this string should be used to identify the exact version of the class.
#documentation methods comment describes the class, its uses
and (if of public interest) its instance and classvariables.
In many classes, you will find an addition #examples method.
This will contain a comment giving typical uses
(often ready to select & doIt).
These methods consist of comments only; they are not meant to be executed.
(actually, if evaluated, they will return the receiver; since empty methods
are semantically equivalent to a '^ self' method).
The
SystemBrowser
automatically shows the documentation text
(found either in the "documentation" method or in the class comment)
whenever a class is selected.
Thus, to be nice to other people browsing through the system,
you should add a short description of what your class is about in these
documentation methods.
Also, the document viewer can extract a classes documentation methods text and present it cutely formatted - you get your documentation almost for free, if you stick to these conventions !
Do not worry about memory usage when creating documentation methods - simple methods
which return self (as empty methods do) all share a common piece of code,
so there will NOT be thousands of empty methods filling
up your memory.
(to be exact: there is some little overhead per method created
by the method object itself - not by the methods code.
However, for production code, all documentation methods can be easily
removed. Also, stc provides a command line argument, to skip
all methods in the documentation category;
to allow building more compact class libraries.)
If you don't like the above, use the classes comment string, which also does not eat up memory (it used to before release 2.10.3 of Smalltalk/X).
BTW: from the authors experience, you should not delay documentation too much. Write them down as soon as possible - otherwise you may not find the time to do so later - or you may simply forget to do it. Also, keep in mind that it may take more time to add those comments later, since you may have to reflect about what is going on. From our experience, the later the documentation is written in a project, the higher is its cost.
#version method, which
must return the classes version string.
"$Header$" ;
for sccs, it is: "%W% %H%").
Array class, you will see something like:
version
^ '$Header: /cvs/stx/stx/doc/online/english/programming/codingStyle.html,v 1.20 2003/03/26 15:40:04 cg Exp $'
AbstractSourceCodeManager)
if others base mechanisms (for example: sccs) are to be used.
If such a version method is present, and the sourceCodeManager is enabled, access to the repository is possible from the browser, and done automatically to retrieve a classes source (based on the actual version of that class in the system).
When classes are checked in via the browser, these version methods are automatically created (if not already present) - there is also a menu function, to create those manually.
In normal operation, the handling of those is transparent, and you can safely forget about it ... its useful to know about it, anyway).
Please, use this type of comment, since ST/X provides special printout
features, which allow you to create printed documentation automatically,
based on these comments (similar to javadoc).
See the
SystemBrowsers printOut protocol
functions - and the
online class documentation.
select:aBlock
"return a new collection
with all elements from the receiver,
for which the argument aBlock evaluates to true"
|newCollection|
newCollection := self species new.
self do:[:each |
(aBlock value:each) ifTrue:[
newCollection add:each
].
].
^ newCollection
"
#(1 2 3 4) select:[:e | e odd]
(1 to:10) select:[:e | e even]
"
By convention, global variables and class variables should start with an upper case character - other variables and selectors by a lower case chracter.
In many situations, a global can be eliminated by
by passing additional method arguments
(which may even be an advantage later,
offering more possibilities for reuse of a method).
Almost all globals can easily be
replaced by a private classVariable instead and access be provided to
other parts via class methods.
sum := sum + 1. "add one to sum"
is stupid and filling your methods with this kind of "information"
actually makes your code less readable.
Also, if you think that a variable needs a comment stating its use,
think about changing the variables name;
For example, the following code is a (stupid) example for a bad variable name:
why not name the variable "counterForBlaBla" right away ?
|c "counter for blabla"|
And, similar, if a group of statements need an explanation as in:
I would suggest that you extract those statements into a separate method,
name it "readDataFromFileBlaBla" and invoke that method.
...
"read the data from file blabla"
...code to read data...
...and so on...
...
Voila - the methods name is just as good as the original comment.
The above actually means, that as your code becomes more more readable and less-cryptic,
less comments are needed.
However, if you use special tricks or uncommon constructs,
you should add a comment describing what is going on -
for yourself and for others.
In the documentation you may see comments explaining obvious things
(like: " ... now open the view ...") which seems to violate
the above.
The reason is that we expect these texts to be read also by newcomers -
therefore, more than usual is often commented there.
foo
"this method performs some fooBar.
Sometimes even baz is done"
doingBar
ifTrue:[
self fooBar.
[doingBaz]
whileTrue:[
self baz].
self moreFooBar.
1 to:10 do:[:index |
1 to:10 do:[:index2 |
self doMore]]]
ifFalse:[
...
and so on]
This style of indentation is seen often in ST-80 code.
(the ST-80 formatter seems to automatically produce output in this format).
Some variations are possible, for example, you can put the ifTrue:
and whileTrue: right behind the receiver (as below).
foo
"this method performs some fooBar.
Sometimes even baz is done"
doingBar ifTrue:[
self fooBar.
[doingBaz] whileTrue:[
self baz
].
self moreFooBar.
1 to:10 do:[:index |
1 to:10 do:[:index2 |
self doMore
]
]
] ifFalse:[
...
and so on
]
Also, variations are possible; for example, the opening brackets of blocks
can be put onto a separate line, as in:
foo
"this method performs some fooBar.
Sometimes even baz is done"
doingBar ifTrue:
[
self fooBar
[doingBaz] whileTrue:
[
self baz
].
self moreFooBar.
1 to:10 do:
[:index |
1 to:10 do:
[:index2 |
self doMore
]
]
]
ifFalse:
[
...
and so on
]
However, this seems to spread the code for even small methods quite a bit.
To many programmers, this makes the readability worse.
Many other styles are possible, however, whichever you choose, follow these rules:
Often, some alternative must return from a method after some other method is invoked.
A typical example are guards, as in:
Do not ``save some typing'' by writing "^ self doSomethingForFoo" .
...
foo ifTrue:[
self doSomethingForFoo.
^ self
].
bar ifTrue:[
self doSomethingForBar.
^ self
].
...etc...
...
(even if "doSomethingForFoo" is known to always return the receiver).
The reason for this is that it does obfuscate the fact that you are not interested in the answer from
"doSomethingForFoo", AND (more important) it does obfuscate the return value of the method itself.
A reader of that code has to look into the other method in order to find out what the return value is.
Do not reuse a local variable - a good style is to assign only once (functional style).
These methods not only save typing - they are also documenting what is done.
If you see code like:
is much better written as:
myMethod
someCollection do:[:eachElement | eachElement someCondition ifTrue:[^ true]].
^ false.
Just read it aloud and you know why.
myMethod
^ someCollection contains:[:anElement | anElement someCondition].
Its much better to use an object (a private classes' instance will do), and ask it instead
of comparing symbols.
I.e. instead of:
the first step is to create a state object, and ask it via a test-method:
...
state == #foo ifTrue:[
...doSomethingForFoo...
].
...
state == #bar ifTrue:[
...doSomethingForBar...
].
...
this has a first advantage, of being browsable (senders, implementors etc.) and of being
detected in case of a typing mistake (i.e. a typo as in "state == barr").
...
state isfoo ifTrue:[
...doXForFoo...
].
...
state isBar ifTrue:[
...doXForBar...
].
...
However, once this is done, the obvious next step is to move the above action into the state object,
and get rid of the if, as in:
if required, pass the receiver, as in:
...
state doX
...
the BIG advantage of this becomes apparent, when a new state is added.
...
state doXFor:self.
...
BTW: this is a well-known pattern in OO-programming.
...
aReadStream ioConnection input readWait.
^ aReadStream atEnd not
...
obviously, the readStream object is some kind of Socket-accessing
thingy, and the programmer wants to wait for something to arrive.
Another example is:
here, the widgets internal hierarchy is reflected in the access-path,
and the code will have a very hard time if the wrapper hierarchy ever changes.
...
widget wrapper wrapper wrapper controller enable.
...
These code fragments were specially written for a parcPlace system, but even parcPlace
will not be able to change any internals, without affecting this program.
Thus, there is a chance that the above code will not work without change in the next
parcPlace version (we dont even think of porting it without change to another smalltalk dialect).
A better solution was to provide a #readWait in the class of the socket-accessing object, and delegate things there, and to provide an enable in the widget, which delegates it to some controller.
This hides the object structure and allows both parcPlace to change things, and allows ST/X's Socket class to behave like other SocketAccessors.
Of course, the above examples do not only apply to system classes - the same is often found in user code (i.e. GUI code).
Copyright © 1995 Claus Gittinger Development & Consulting
<cg@exept.de>