Archive for April, 2010

Measuring Simplicity

April 30, 2010

In a recent discussion I was pointing out that in software design I prefer simplicity over coolness, and got the following comment back:

“simple” is not so much more precise than “cool”

This got me thinking. We know a lot about measuring complexity, it may be true that we can’t put an absolute measure on simplicity, but I’m sure we can measure relative simplicity a whole lot more than “coolness”. Let’s give it a shot.

First, we need a measure of simplicity and I’ll suggest to use the number of entities involved. That seems like an obvious and straightforward measure. Something that is constructed out of 5 parts is probably simpler than something that is constructed out of 50. A program with 900 LOC is probably more similar in complexity to a program with 1100 LOC than a program consisting of 50 kLOC. A library exposing 30 classes is probably simpler to use than one exposing 100 classes and a class with 15 methods is probably simpler than one one with 30 methods. There will be exceptions but as a rule of thumb it seems like a good first order approximation.

Next, what dimensions are we going to look at? In software there are different aspects that are important. I’ll propose the following three dimensions:

  • Conceptual Simplicity, measuring how many entities we need to understand what the software does,
  • Interface Simplicity, measuring how many entities we need to understand how to use the software, and
  • Implementation Simplicity, measuring how many entities we need to understand how the software does it.

As an example for these dimensions, let’s consider Smalltalk execution semantics. The conceptual model is quite simple, linked frames that are stored in the heap (a more detailed explanation is here). The interface isn’t quite as simple, there are several classes and many operations (push, pop, temp access, reflection etc) involved. The implementation, in particular when trying to implement the model efficiently, can be highly complex (check out Eliot’s comments on context to stack mapping in Cog).

Now that we’ve got both a value as well as dimensions to look at, let’s apply our measures to see if simplicity can’t be a reasonably precise measure. I’m going to compare MVC and Morphic which are both UI frameworks in Squeak and I’m going to compare the versions in Squeak 4.1 specifically.

In terms of conceptual simplicity, MVC has three entities (Model, View, and Controller) whereas Morphic only has a single entity (Morph). That’s a clear win for Morphic, conceptually, it is simpler than MVC. In terms of interface simplicity there can be no doubt: With 324 Morph classes and 128 Controller+Model+View classes MVC wins hands down in Squeak (the actual ratio is much worse since this counts all of the tools which are subclasses of Model for historical reasons). And again, anyone who has used both will probably agree, that MVC in Squeak has a simpler interface even if it is more complex conceptually. For the implementation of both MVC and Morphic we can use the same values which hands victory to MVC.

(as a side note I’d like to point out that interface and implementation simplicity don’t necessarily go hand in hand. For example, consider memory management. Using memory management functions like malloc() and free() correctly is much more complex than using a garbage collector, but the implementation of a malloc/free allocator is certainly simpler than implementing a garbage collector)

As a result of applying our measures to Morphic and MVC we get a pretty good idea of relative simplicity along various dimensions. It’s not an “exact” measure, and it’s not an “absolute” measure but it is a measure that allows us to compare and contrast software and measure its relative simplicity.

PS. The facade pattern is a great example for explicitly increasing interface simplicity of some subsystem.


Annotations for Service Discovery

April 27, 2010

There has been an interesting discussion going on in squeak-dev about “annotations for service discovery”. Stated simply, the problem is how a third party package can safely extend system-level services in the face of rapid evolution of the underlying system. In Squeak, this mostly affects the following three areas:

  • Preferences. Packages often want to show preferences in the standard preferences tools.
  • File services. Packages that provide services to operate on files, need to register these.
  • Menu items. Packages providing UI elements want to register these with the proper menus.

It sounds simple enough. In practice, these simple things get very messy, very quickly due to the evolution of the system. Here is how it goes: First, you have a new feature and people just use it, say:


	FileList registerFileReader: self.

MyClass>>fileReaderServicesForFile: fullName suffix: suffix
	"Return the provided services"
	^suffix = 'mysuffix' ifTrue:[
			provider: self
			label: 'myclass service'
			selector: #doServiceWith:
			description: 'performs a service'
			buttonLabel: 'myclass'}
	] ifFalse:[#()]

That’s nice and well, unless you realize that you also want to support older Squeak versions. Ugh. Now we get this:


	(FileList respondsTo: #registerFileReader:)
		ifTrue:[FileList registerFileReader: self].

This results in additional Undeclared variable from SimpleServiceEntry. Moving forward, the system keeps evolving, and the services were factored out of FileList and moved to FileServices. Our initializer then becomes

	Smalltalk at: #FileServices ifPresent:[:aClass|
		^aClass registerFileReader: self
	"The legacy variant"
	(FileList respondsTo: #registerFileReader:)
		ifTrue:[FileList registerFileReader: self].

Etc. I think you can see where this is headed. Every time something changes, we must update our code to play with the latest changes in the underlying system. It is our responsibility to make sure that the code doesn’t break, making it necessary to adapt the code to any changes going on.

The question is: How can we fix that? How can we avoid breaking such extensible services in the first place?

The answer: Method annotations (“pragmas”). They provide an excellent way to discover services provided by a class. Instead of having the class “patch” the system (i.e., via calling methods on various registries directly) the system “discovers” the service provided by a class through the use of annotations.

At this point I need to apologize since I’m shifting gears. The (real) examples above were taken from FileList because that’s where the problems occurred but down below I’ll be using preferences to illustrate how annotations solve the service discovery problem. That’s because we have only implemented them for preferences and so that’s what I’m using here.

To give an example, here is how preferences used to be created and used in Squeak:

Locale class>>initialize
	"Locale initialize"

	Smalltalk addToStartUpList: Locale.
	Preferences addPreference: #useLocale
		categories: #('general') default: false
		balloonHelp: 'Use the system locale to set the system language etc at startup'.

Locale class>>startUp: resuming
	"... snip ..."
	(Preferences valueOfFlag: #useLocale)
		ifTrue: [
			newID := self current determineLocaleID.
			newID ~= LocaleID current
				ifTrue: [self switchToID: newID]]

Basically, all preference uses were hardcoded to go through Preferences with a very rich (friendly for “bloated”) interface for manipulating preferences. The implementation was then tied into various UI aspects making it virtually impossible to simplify or decouple them.

To fix this, we introduced the annotation #preference:category:description:type: used for example here:

Editor class>>blinkingCursor
	<preference: 'Blinking Text Cursor'
		category: 'Morphic'
		description: 'When true, the text cursor will blink.'
		type: #Boolean>
	^ BlinkingCursor ifNil: [ true ]

The first thing to notice is that in the client code we’ve put away with an explicit representation for “Preferences”. A preference, as far as a package is concerned is simply a value that you would like to be settable by the user. You can always get and set the value by asking Editor for its #blinkingCursor value – the fact that this value should also be shown as a preference is orthogonal to its existence as a value all in itself.

Secondly, the declaration abstracts away any dependency on the implementation. Any code that deals with preferences and sees this annotation has all the information it needs to present the preference to the user. As a consequence, the only code that is here is the actual domain code – there is no code that deals with the concept of “preferences” in any way. In fact, there may not even be a preference implementation in the system.

Thirdly, using an annotation this way is shifting the burden from the package maintainer to the system maintainer. The package maintainer is no longer required to update their package for each and every Squeak version – it is the task of the system maintainer to implement the service discovery in the proper form.

The scheme described here extends naturally to the other uses mentioned above. In each case we have a “domain action”, be that setting a value (for preferences as in “Editor blinkingCursor”) a method invocation (for a menu, say “Browser open”) or a file operation (for the file list, “SampledSound playWaveFileNamed: …”) that the package provides. In each case we have some additional form in which we’d like to provide that service to the system (via preferences browser, menus, file list operations) and in each case we can announce the availability via an annotation that is discovered by the system.

As a consequence, everyone wins. The package maintainer wins because she has less work to do, deals only with domain code and gets backwards (by backporting the discovery) and implied forwards compatibility. The system maintainer wins because they get the freedom to change core parts of the system without fear of causing undue breakage. A classic win-win situation.


April 23, 2010

Hi there. I’ve always needed a blog for some of my very own comments on Squeak and the rest of the world, I finally gave in and started this one.