With Style
With Style
Stéphane Ducasse
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following conditions:
Attribution. — You must give appropriate credit, provide a link to the license, and
indicate if changes were made. You may do so in any reasonable manner, but
not in any way that suggests the licensor endorses you or your use.
NonCommercial. — You may not use the material for commercial purposes.
NoDerivatives. — If you remix, transform, or build upon the material, you may not
distribute the modified material.
No additional restrictions. — You may not apply legal terms or technological measures
that legally restrict others from doing anything the license permits.
https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
Any of the above conditions can be waived if you get permission from the copyright
holder. Nothing in this license impairs or restricts the author’s moral rights.
Layout and typography based on the sbabook LATEX class by Damien Pollet.
Contents
Illustrations iv
3 Selectors 15
3.1 Guideline: Choose selectors to form short sentences . . . . . . . . . . . . . 15
3.2 Guideline: Use imperative verbs for actions . . . . . . . . . . . . . . . . . . 15
3.3 Guideline: Prefix with as for conversion . . . . . . . . . . . . . . . . . . . . 16
3.4 Guideline: Indicate flow with preposition . . . . . . . . . . . . . . . . . . . 16
3.5 Guideline: Indicate return types . . . . . . . . . . . . . . . . . . . . . . . . 17
3.6 Guideline: Use interrogative form for testing . . . . . . . . . . . . . . . . . 18
3.7 Guideline: Avoid using parameter and variable name type . . . . . . . . . . 18
3.8 Guideline: Use accessors or not . . . . . . . . . . . . . . . . . . . . . . . . 19
3.9 Guideline: Name accessors following variable name . . . . . . . . . . . . . 19
3.10 Guideline: Avoid return in lazy block . . . . . . . . . . . . . . . . . . . . . 20
i
Contents
4 Comments 25
4.1 Guideline: Method comments . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2 Guideline: Avoid relying on a comment to explain what can be reflected
in code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 Guideline: Use active voice and short sentences . . . . . . . . . . . . . . . 27
4.4 Guideline: Include executable comments . . . . . . . . . . . . . . . . . . . 27
4.5 Guideline: Use CRC-driven class comments . . . . . . . . . . . . . . . . . . 28
4.6 Guideline: Comment the unusual . . . . . . . . . . . . . . . . . . . . . . . 28
7 Object initialization 41
7.1 Guideline: Take advantage of automatic object initialization . . . . . . . . . 41
7.2 Guideline: No automatic initialize . . . . . . . . . . . . . . . . . . . . . . . 42
7.3 Guideline: No double super new initialize . . . . . . . . . . . . . . . . . . . 42
8 Potential traps 43
8.1 Guideline: Use parentheses to disambiguate messages with the same priority 43
8.2 Guideline: no need for extra parentheses . . . . . . . . . . . . . . . . . . . 44
8.3 Guideline: receiver of ifTrue:ifFalse: is a boolean . . . . . . . . . . . . . . . 46
8.4 Guideline: receiver of whileTrue: is a block . . . . . . . . . . . . . . . . . . 46
8.5 Guideline: Use a Block when you do not know execution time . . . . . . . . 47
8.6 Guideline: initialize does not need to return super . . . . . . . . . . . . . . 47
8.7 Guideline: super is just self . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.8 Guideline: use super to invoke a method with the same selector . . . . . . . 49
ii
Contents
10 Use Patterns 55
10.1 Transcript: A misunderstood object . . . . . . . . . . . . . . . . . . . . . . 55
10.2 First simple case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
10.3 The real concern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
10.4 Encapsulation to the rescue . . . . . . . . . . . . . . . . . . . . . . . . . . 57
10.5 When instance creation is delicate . . . . . . . . . . . . . . . . . . . . . . 57
10.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
10.7 About error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
11 Conclusion 61
iii
Illustrations
iv
Illustrations
1
CHAPTER 1
General naming conventions
Names are important. We will never repeat it enough. A good name is often
driven by a domain.
over
Not timeofday
Not time_of_day
Prefer
GnatXmlNode
over
Not GNAT_XML_Object
Prefer
3
General naming conventions
releasedX
over
released_X
Still, Pharo favors camel case, so use it systematically for words. Wikipedia
defines camel case as: Camel case (stylized as camelCase) is the practice of
writing phrases such that each word or abbreviation in the middle of the
phrase begins with a capital letter, with no intervening spaces or punctua-
tion.
For local variables, method parameters and instance variables, use
maxLimit
instead of
Not maxlimit
Not MAXLIMIT
instead of
Not ORDEREDCOLLECTION
Not MAXLIMIT
4
1.4 Guideline: Use descriptive names
Prefer
superclass
over
Not superClass
over
Not tod
Prefer
milliseconds
over
not mil
Prefer
editMenu
over
not eMenu
over
Not ParserForDate
Prefer
userAssociation
5
General naming conventions
Not associationUser
over
GetMethodsNamesFromAClass: aClass
| methodsNames |
methodsNames := aClass selectors.
methodsNames do: [ :each | names add: each ]
Also, in this example the method selector is not good because method names
are called selectors in Pharo. In addition in English methodsNames should be
written methodNames. It should be gatherSelectorsFrom: or something
similar.
over
not GNAT_XML_Object
When representing the XML Ada abstract syntax tree in Pharo, we should not
follow Ada naming conventions. The name should convey that the class is an
6
1.8 Guideline: Shared variables start with uppercase
abstract syntax tree node. Hence GnatXmlNode is much better than GnatXm-
lObject.
Prefer
| dataset f xMatrix scale x |
over
| dataset f Xmatrix scale X |
over
| dataset pca scaled_X reduced_X |
7
General naming conventions
Not readSize
Does this mean that the size was just read (red) or is it the size to read (reed)?
over
Not ClyBrowserButton
Prefer
ClyQueryViewMorph
over
Not ClyQueryView
over
Not ApplicationWithToolbar
In the next example, DynamicWidgetChange does not convey that this is not
a domain object representing a change, but a Presenter object in the Model-
View-Presenter triad:
DynamicWidgetChangePresenter
over
8
1.13 Guideline: Avoid name collisions
Not DynamicWidgetChange
Note You may find that Pharo is lacking a namespace. If you have a
couple hundred thousands euros, we can fix that!
Note, however, that even with a namespace you will have to pay attention
that your namespace name does not collide with another one.
9
CHAPTER 2
About variable names
over
"Typed variable"
anInteger := numberOfAdults size max: numberOfChildren size
Note that semantic name can convey variable roles. Having more informa-
tion is definitively useful for clients of the code.
Prefer
11
About variable names
"Semantic variable"
selectFrom: aBeginningDate to: anEndDate
over
"Type variable"
selectFrom: aDate to: anotherDate
over
Not properties: map
You may also want to stress specific types in an API referencing to interface
that the object implements.
Prefer
properties: aPuttable
over
Not properties: aCollection
Suppose a String, a Symbol, and nil are valid for a parameter. A developer
may be tempted to use the name aStringOrSymbolOrNil.
You may be tempted to aString or anObject. anObject is not really helping
the developer that will have to use such variables. At the minimum such a
use should be accompanied with a comment that says, ”anObject can be a
String or aSymbol”
Some developers may argue that type variables should not refer to classes
that do not exist. We disagree. As shown in the following guideline it is a
lot better to indicate that an argument is block expecting two arguments
(hence a binary block) than to just mention a block. And this even if there is
no binary block class in the system.
Prefer
inject: anObject into: aBinaryBlock
12
2.3 Guideline: Get the best from semantic and type variable
over
inject: anObject into: aBlock
Note that for inject:into: the best naming is to mix semantic ant type
naming as in
inject: initialValue into: aBinaryBlock
2.3 Guideline: Get the best from semantic and type vari-
able
A good practice is to use a mixture of both semantic and typed variable names.
Method parameter names are usually named after their type. Instance, class,
and temporary variables usually use a semantic name. In some cases, a com-
bination of both can be given in the names.
Prefer
ifTrue: trueBlock ifFalse: falseBlock
over
Not ifTrue: block1 ifFalse: block2
Not ifTrue: action1 ifFalse: action2
over
Not number
Not labelForPerson
13
About variable names
Note that you can also use count instead of numberOf as in the following ex-
ample:
numberOfTires
tireCount
14
CHAPTER 3
Selectors
Method names in Pharo are called selectors. They are used in messages and
are the main vehicle to convey adequate meaning. The correct use of words
and design of selectors is important.
Write the test first, and make sure that your test scenario reads well.
Prefer
aReadStream peek
over
Not aReadStream word
15
Selectors
Prefer
aFace lookSurprised
aFace beSurprised
over
Not aFace surprised
skipSeparators
and
triggerOptimization
16
3.5 Guideline: Indicate return types
Prefer
ReadWriteStream on: aCollection.
over
Not ReadWriteStream for: aCollection.
Prefer
File openOn: stream
over
Not File with: stream
Prefer
display: anObject on: aMedium
over
Not display: anObject using: aMedium
17
Selectors
A good example is the API of the FileReference class. The message path-
String indicates clearly that it returns the path as a string while to access
the path object the message path should be used.
over
Not atLineEnd
aVehicle hasFourWheels
over
Not aVehicle fourWheels
over
Not fileSystem atKey: aKey putFiIe: aFile
Prefer
aFace changeTo: expression
over
Not aFace changeExpressionTo: expression
18
3.8 Guideline: Use accessors or not
over
getTiles
^ tiles
Watch out.
Pay attention a Setter is just setting a value and just returning (implicitly)
the receiver. The following setter definition is not correct.
BinaryNode >> root: rootNode
"Set a root node"
^ root := rootNode
19
Selectors
root := rootNode
For setters
tiles: aCollection
tiles := aCollection
Put accessors in the 'accessing' protocols. When you have accessors doing
extra work place them in a separate protocols to stress their difference.
Over
tiles
^ tiles ifNil: [ tiles := OrderedCollection new. ^tiles ]
or even
tiles
tiles ifNil: [ tiles := OrderedCollection new ].
^ tiles
over
20
3.12 Guideline: Use basic or raw to access low-level
setTiles: aCollection
tiles := aCollection
Following K. Beck’s advice, use setTitles: only for private messages to ini-
tialize objects from class side methods.
basicMethod: aCompiledMethod
method := aCompiledMethod
When you have a getter that returns an object and a getter than return a dif-
ferent representation of the same object add a suffix
path
^ path
pathString
^ self path asString
over
stylesheet get: #fontColor
Prefer
aCollection groupedBy: [ :each | each odd ]
over
Not aCollection groupBy: [ :each | each odd ]
21
Selectors
Prefer
series at: #k3 put: 'x'.
over
Not series atKey: #k3 put: 'x'
Prefer
aCollection at: #toto
over
Not aCollection atKey: #toto
Example
In Pillar, annotations have parameters and the accessor method parame-
ters:. Now, in some version, an instance creation method with the same
name than the accessor method.
Reading the code of the parser, it is not clear whether array second is a class
or an instance.
annotation
^ super annotation
==>
[ :array | array second parameters: (array third ifNil: [
SmallDictionary new ]) ]
22
3.15 Guidelines: Follow existing protocols
| parameters |
parameters := self checkKeysOf: aCollection.
^ self new
hadAllKeys: aCollection = parameters;
parameters: parameters;
yourself
is better than
PRAbstractAnnotation class >> parameters: aCollection
| parameters |
parameters := self checkKeysOf: aCollection.
^ self new
hadAllKeys: aCollection = parameters;
parameters: parameters;
yourself
Now if you favor a fluid interface with many parameters, using withParame-
ters: may not be good.
23
CHAPTER 4
Comments
Comments are important. Comments tell readers that they are smart guys
and that they correctly guessed your intentions or your code. Do not believe
people that say that methods do not need comments. Obviously here is what
they mean:
1. obvious methods such as accessors do not need comments,
2. a good comment is not describing in English how the code executes,
3. it is better to split long methods into smaller ones with a single respon-
sibility,
4. but a good comment is always welcome because it reinforces the un-
derstanding of the reader.
A comment should be adapted to the level of granularity (i.e., package, class,
method) to which it applies.
25
Comments
^ day
over
| result |
"Store the employees who have a salary greater than in result."
result := self employees
collect: [:employee | employee salary > amount].
26
4.3 Guideline: Use active voice and short sentences
| result |
result := OrderedCollection new: (aSequenceableCollection size /
2) asInteger.
self split: aSequenceableCollection do: [ :item |
result add: item ].
^ result
27
Comments
Implementation Points
28
CHAPTER 5
About code formatting
29
About code formatting
| temporary variables |
statements
For example:
addLast: newObject
"Add newObject to the end of the receiver. Answer newObject."
Do not let space before the first word of the comment, align comment with
method body, make sure that the reader can identify the beginning of the
method body by placing an empty line between the method signature and
the method body.
Prefer the following
collectionNotIncluded
"Return a collection for wich each element is not included in
'nonEmpty'"
^ collectionWithoutNil
over:
collectionNotIncluded
" return a collection for wich each element is not included in
'nonEmpty' "
^ collectionWithoutNil
and over:
collectionWithoutEqualElements
30
5.4 Guideline: Separate signature and comments from method body
Prefer
initialize
super initialize.
symbols := Bag new.
names := Set new
over
performCrawling: aName
"Takes the last word in uppercase as a symbol and eventually add
it to the bag symbols"
name := aName copy.
self getUpperCase.
self stemSymbolFrom: aName.
self toUpperCase.
^ symbol
31
About code formatting
| stemmer symbol |
stemmer := SymbolStemmer new.
symbol := stemmer performCrawling: aName.
^ symbol
Over
stemSymbolFrom:aName
|stemmer symbol|
stemmer:=SymbolStemmer new.
symbol:=stemmer performCrawling:aName.
^symbol
Parentheses do not need spaces (after ( and before )) since they show that
an expression fits together. But favor space for [ and ], since they may con-
tain complex expressions.
drawOnAthensCanvas: aCanvas bounds: aRectangle color: aColor
Over
32
5.7 Guideline: Use tabs not spaces to indent and use spaces to help reading
5.7 Guideline: Use tabs not spaces to indent and use spaces
to help reading
When navigating from one element to the next one, spaces and tabs are the
same. Since they do not have a visual representation the reader cannot know
in advance if the white space in front of word is a tab or multiple spaces. It is
then annoying to handle spaces manually.
• Avoid extra spaces at the beginning and use tabs to indent.
• Use one space to separate instructions.
• Avoid extra spaces everywhere: one is enough!
• Avoid extra spaces at the end of the line.
Prefer
stemSymbolFrom: aName
| stemmer symbol |
stemmer := SymbolStemmer new.
symbol := stemmer performCrawling: aName.
^ symbol
over
stemSymbolFrom: aName
|stemmer symbol|
stemmer:=SymbolStemmer new.
symbol:=stemmer performCrawling: aName.
^symbol
33
About code formatting
paragraph
"this method is here to find the paragraph in the chain, instead
of relying on implementing #doesNotUnderstand: !!!"
| p |
p := next.
[ p isNotNil and: [ p isKindOf: RubParagraph ] ]
whileFalse: [ p := p next ].
^ p
Over
paragraph
"this method is here to find the paragraph in the chain, instead
of relying on implementing #doesNotUnderstand: !!!"
| p |
p := next.
^p
Over
size
"Returns size of a tree - number of nodes in a tree"
self root isNil
ifTrue: [ ^0 ].
^self size: self root.
And prefer
34
5.9 Guideline: Highlight control flow
depth: aNode
"Returns depth of a tree starting from the given node"
| leftDepth rightDepth |
leftDepth := -1.
aNode leftChild isNotNil
ifTrue: [ leftDepth := self depth: aNode leftChild ].
rightDepth := -1.
aNode rightChild isNotNil
ifTrue: [ rightDepth := self depth: aNode rightChild ].
leftDepth > rightDepth
ifTrue: [ ^ 1 + leftDepth ]
ifFalse: [^ 1 + rightDepth ].
35
CHAPTER 6
Powerful coding idioms
Some coding idioms will make your code a lot clearer. Knowing them is also
good because you will code faster.
is better than:
| that |
that := self doThat.
that ifNotNil: [self doSomethingWith: that]
37
Powerful coding idioms
depth: aNode
"Returns depth of a tree starting from the given node"
...
^ leftDepth > rightDepth
ifTrue: [ 1 + leftDepth ]
ifFalse: [ 1 + rightDepth ]
over
depth:aNode
"Returns depth of a tree starting from the given node"
...
leftDepth > rightDepth
ifTrue: [ ^ 1 + leftDepth ]
ifFalse: [ ^ 1 + rightDepth ]
over
stream := WriteStream on: (String new: 1000).
stream ...
^ stream contents
38
6.4 Guideline: Avoid , when in loop
Difference in speed
The following snippets shows the difference in execution on large concatena-
tions.
[ String streamContents: [:s | 1 to: 10000 do: [ :i | s << i
asString ]] ] bench
>>> '551.890 per second'
[ | s |
s := ''.
1 to: 10000 do: [ :i | s := s, i asString ] ] bench
>>> '8.465 per second'
[ | s |
s := ''.
1 to: 1000 do: [ :i | s := s, i asString ] ] bench
>>> '967.819 per second'
39
CHAPTER 7
Object initialization
One key responsibility for a class is to initialize correctly the instance it cre-
ates. This avoids to place the burden on clients of such objects. In this chap-
ter we will present some patterns to initialize objects.
Imagine that we implement a game and that this game needs to initialize the
number of turns the game is played. Avoid to force the clients of the game
to have the responsibility to initialize the turn. Indeed if the clients forget to
send the expression turn:, the game logic may be simply broken.
Game new turn: 0
Therefore when you want to initialize the state of your object, simply rede-
fine
Game >> initialize
super initialize.
turn := 0.
This way Game new will automatically set the value for turn.
From a test perspective, it is often a good practice to have a test covering the
default initialization.
41
Object initialization
Let us imagine that the computation of the tiles of a game would be costly
or that there is no simple default. We can propose a class creation interface
whose responsibility is to request the mandatory information and initialize
only what is needed for the object creation.
Here we define a class method tileNumber:, and we do not invoke new but
basicNew. Indeed new sends the message initialize and we do not want
to pay the price for it. basicNew just allocates the new object and does not
perform any other operation.
Game class >> tileNumber: aNumber
^ self basicNew
initializeTiles: aNumber ;
yourself
Now on the instance side we can define the initialization of tiles as shown by
the method initializeTiles:.
Game >> initializeTiles: aNumber
tiles := Array new: aNumber
In Pharo, the method initialize of the class Game will be invoked twice.
42
CHAPTER 8
Potential traps
For keyword-messages
The Pharo compiler does not know where to cut an expression composed
on multiple keyword-based messages. For example, assert:includes: in the
expression self assert: uUMLClass variables includes: 'name' can
be a message that the object self can understand. For example testcases
understand the message assert:equals: and the following expression is
fully valid: self assert: uUMLClass variables equals: 'name'.
Therefore, this is the programmer responsibility to use parenthese to sepa-
rate correctly the messages having the same priority. The following example
illustrates this point.
testDefineASimpleClass
| uUMLClass |
uUMLClass := UMLClass named: 'ComixSerie'.
uUMLClass instVar: 'name'.
self assert: uUMLClass variables includes: 'name'
43
Potential traps
| uUMLClass |
uUMLClass := UMLClass named: 'ComixSerie'.
uUMLClass instVar: 'name'.
self assert: (uUMLClass variables includes: 'name')
over
xMatrix := PMMatrix rows: ( x asArrayOfRows ).
44
8.2 Guideline: no need for extra parentheses
over
reducedX do: [ :row |
( (row at: 'target') = 'Iris-setosa')
ifTrue: [ a add: (row asArray) ] ].
Prefer
depth: aNode
"Returns depth of a tree starting from the given node"
| leftDepth rightDepth |
leftDepth := -1.
aNode leftChild isNotNil
ifTrue: [ leftDepth := self depth: aNode leftChild ].
rightDepth := -1.
aNode rightChild isNotNil
ifTrue: [ rightDepth := self depth: aNode rightChild ].
^ leftDepth > rightDepth
ifTrue: [ 1 + leftDepth ]
ifFalse: [ 1 + rightDepth ]
over
depth:aNode
"Returns depth of a tree starting from the given node"
| leftDepth rightDepth |
leftDepth := -1.
aNode leftChild isNotNil
ifTrue: [ leftDepth := self depth: (aNode leftChild) ].
rightDepth := -1.
aNode rightChild isNotNil
ifTrue: [ rightDepth := self depth: (aNode rightChild) ].
45
Potential traps
| a |
a := 12.
{a} printString
>>> #(12)
over
m := (pca transform: xMatrix)
[lastNode =0]
ifTrue:[ lastNode := curNode ]
ifFalse:[ lastNode next: curNode ]
46
8.5 Guideline: Use a Block when you do not know execution time
ifTrue:ifFalse:
The conditional is always executed, while each of the arguments is a block
because we do not know which ones will be executed.
lastNode = 0
ifTrue: [ lastNode := curNode ]
ifFalse: [ lastNode next: curNode ]
timesRepeat:
timesRepeat:’s argument is a block because we do not know how many
times it will be executed.
n timesRepeat: [ lastNode := curNode next ]
do:/collect:
The argument of iterators such as do:, collect:,... is a block because we do
not know how many times (if any) the block will be executed.
aCol do: [ :node | ...]
47
Potential traps
initialize
default := 'no'.
^ super initialize
First it returns a value and this value is not really used. Prefer the following
definition:
initialize
default := 'no'.
super initialize
Second, the ordering implies that the first local information should be com-
puted then the one of the superclass. It may happen that you need this order
but it is rare so prefer the following canonical form:
initialize
super initialize
default := 'no'.
Similarly
foo
^ super
48
8.8 Guideline: use super to invoke a method with the same selector
foo
^ self
For example, the following just creates an infinite loop, since the method
initialize is calling itself infinitively.
initialize
self initialize.
self continue
This is the only case where you must use super to invoke the method that
cannot be reach by the method lookup when it starts from the class defining
the method. The correct definition is then:
initialize
super initialize.
self continue
Now there is no need to use super for message that have a different selector
than the method they belong to. For example, there is no need to use super
bar in the following, since the defining method is called foo and you send
the message bar.
foo
super bar.
self continue
While using super often does not break your code, it may because if you
have a method named bar in the same class this method will not be invoked.
So always be suspicious when you read or write such kind of code.
49
CHAPTER 9
About printing and Streams
aPerson displayString
>>> 'John Doe'
51
About printing and Streams
| limitedString |
limitedString := String streamContents: printBlock limitedTo:
limit.
limitedString size < limit ifTrue: [^ limitedString].
^ limitedString , '...etc...'
52
9.3 Guideline: Unnecessary stream creation
Pay attention, sending the printString message is often wrong and it is the
duties of the system to do it.
What you should see is that a stream is created and then another stream is
created and discarded with the expression protocol printString. A better
implementation is the following using print:.
printProtocol: protocol sourceCode: sourceCode
53
About printing and Streams
The situation is the same for strings but it can be a bit more destabilizing.
The conversion of a string to a string is the string itself.
'foo' asString
>>> 'foo'
Now printing a string includes the quotes and quotes should be double quoted.
'foo' printString
>>> '''foo'''
54
CHAPTER 10
Use Patterns
Some developers may think that this is not important but it can help you
if one day you want to control the logging and for example use an object
55
Use Patterns
with the same API but to do something else. So avoid hardcoding globals.
But there is more.
Transcript << 'Closing ' << self class name; cr; endEntry
So this method is producing a little trace so that the parser developer could
understand what was happening. So you can think that this is ok.
There are two main problems:
• First what if you want to deploy your application in a system where
you do not want at all to get Transcript its class and all its family. I’m
thinking for example about people producing minimal images.
• Second, when Pharo is built on Jenkins all the tests are executed be-
cause we love tests. And this Transcript expression produces dirt on
the build log. You do not want to have read such trace when you are
trying to understand why the build is not working.
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicCodeBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
Closing MicHeaderBlock
56
10.4 Encapsulation to the rescue
Closing MicListItemBlock
Closing MicListItemBlock
Closing MicOrderedListBlock
Now let us see the good way to have a log and be able to unplug it.
Then we can provide a simple setter method so that the developer can set for
example the Transcript as a stream to write to.
MicAbstractBlock >> logStream: aStream
logStream := aStream
57
Use Patterns
We define one setter. This is using this setter that we will be able to say to
the system that for a given execution it should write to the Transcript: for
example doing MicAbstractBlock logStream: Transcript.
MicAbstractBlock class >> logStream: aStream
DefaultStream := aStream
We make sure that the class variable is initialized to a default stream. Now
we do it in way that we can later reset the stream to such default stream.
MicAbstractBlock class >> reset
self logStream: (WriteStream on: (String new: 1000))
Here we hardcode the stream and alternate solution that extract this expres-
sion in a separate messages if it makes sense for subclass to specialize.
MicAbstractBlock class >> initialize
self reset
And we will be able to reset the default solution either by reinitializing the
class MicAbstractBlock initialize or MicAbstractBlock logStream:
MicAbstractBlock defaultValueForStream
Now we make sure that the instance creation uses the default stream hold by
the class.
MicAbstractBlock >> initialize
super initialize.
children := OrderedCollection new.
logStream := DefaultStream
The net result is that we have the control and we can decide what is happen-
ing. In addition we can write tests to make sure that the logging is correct.
Because using Transcript makes this a brittle exercise since someone else
may write to the Transcript when you do not expect it.
10.6 Conclusion
Transcript is not bad per se. But it promotes bad coding practices. Devel-
opers should stop listening to the sirens of easy and cheap global variables.
With a little bit of care and a limited infrastructure is to possible to get the
best of both worlds: modular objects and taking advantages of the existing
infrastructure whose Transcript belongs to.
What we presented about Transcript can be applied to any global object or
singleton. This is not because a library exposes a singleton that you have to
58
10.7 About error
blindly use it. You should apply sound principle and protect you some im-
pact of changes.
59
CHAPTER 11
Conclusion
Remember that you write code once and will read it a thousand times. Take
the time to give good names. However finding good names is not an easy
task, but you can use refactorings to improve things easily. This goes in pair
with tests. You write a test once and it gets executed million times. There-
fore, write tests to exercise the names you use and change them until they
help you telling stories that can be understood.
61