In this blog post, I will discuss why using Transcript can be really badly used. I will present that with some simple care we can develop modular solutions that are flexible and can take advantages of using Transcript without the inconvenients.
As a general remark, if you want to log better use a real system logging PLAIN real objects and not just dead strings. Because you can do a lot more with objects than mere strings. You can use Beacon (whose core is available in Pharo by default)http://www.humane-assessment.com/blog/beacon or other logging frameworks as the one developed by Cyril Ferlicot and based on dynamic variables https://github.com/jecisc/TinyLogger.
Now let us imagine that you still want to log strings.
Transcript: A misunderstood object
Transcript is a kind of stdout on which you can write some strings outputs. It is cheap. The class exposes a stream-based API (and this is a really important design point as we will see in the future).
Here is a typical bad use of Transcript
myMethod Transcript show: 'foo' ; cr
It is bad because it hard codes a reference to transcript while Pharo proposes some helpers methods such as traceCr:
myMethod self traceCr: 'foo'
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 with the same API but to do something else. So avoid hardcoding globals. But there is more.
The real concern
The problem amongst others is that Transcript is a singleton and in fact an UGLY global variable. Once you use it for real in your code, you basically killed the modularity of your program and the only thing that you can do is to hope that nothing bad can happen.
Let us look at a concrete simple case. The microdown Parser came (yes we removed this) with a simple method named closeMe:
MicAbstractBlock >> closeMe 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 because 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 Closing MicListItemBlock Closing MicListItemBlock Closing MicOrderedListBlock
You can say that I’m exaggerating. But let us see the good way to have a log and be able to unplug it.
Encapsulation to the rescue
The solution is really simple. Just use object-oriented programming and encapsulation. To support the Parser developer, we can simply add a stream to the class.
For example we define a new variable to and initialize it to a write stream.
MicAbstractBlock >>initialize super initialize. logStream := WriteStream on: (String new: 1000)
Then we can rewrite the method closeMe as follows
<< 'Closing ' << self class name; cr
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
If we do not control the creation of instances of the class using the stream, we will have difficulties to configure it. So if want to be able to configure the class to use a different logger, we can define a class variable so that we can send message to the class and at initialization time, we can take the value from the class variable instead of hardcoding the WriteStream.
The net result is that we have the control and we can decide what is happening. 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.
Transcript is not bad per se. It promotes bad coding practices. Developers 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.