3

I am writing classes in Pharo Smalltalk, but I assume the question is valid for other Smalltalk implementations.

I know a way to enforce instances with specific attributes is to provide a class method for instance creation, and then suggesting to use the class creation method. But any user know that new or basicNew can be used anytime.

I thought about invalidating new and basicNew raising an exception, but this seems to be too drastic measure, giving that sometimes I could require to create instances to debug for example.

Is there another library or mechanism to enforce those specific attributes to be completed?

user1000565
  • 927
  • 4
  • 12

2 Answers2

8

There is not. And this is good.

Here are some approaches you might follow though:

  1. Make sure your objects are valid by providing validations for them. This is a very broad topic so I will just say that a validation framework is one able to examine your objects and somehow make it explicit any rules they fail to honor.

    Depending on what you are doing, you might restrict validations to the GUI, when objects are born or modified. A more sophisticated approach, however, should allow any object to decide whether it is valid or not (in a given context).

  2. Exclude individual setters from the instance-side protocol. Provide only multiple keyword setters that will fail if something is missing or inappropriate. This means that you would provide methods like Point >> #x:y: but not Poit >> #x: or Point >> #y:.

    As the example suggests, you will find it hard to take this practice at its full extent because basic classes don't follow this style. Also note that this practice will require some kind of validation because checking only for notNil is usually too naïve.

  3. Relax and do nothing until the need arrives for addressing these kinds of issues.

    In other words pospone the problem until your software is evolved enough as to call your attention to the way its objects are created and change.

The reason I claim things are good the way they are is because Smalltalk, having traditionally favored openness over security, has facilitated and even encouraged people to experiment, even in the "wrong" way. Every feature aimed at preventing objects from being broken, will sooner or later stop you from repairing them. More importantly, they will consume a lot of energy that could otherwise be applied to more productive ends.

Community
  • 1
  • 1
Leandro Caniglia
  • 14,495
  • 4
  • 29
  • 51
  • 2
    I'd recommend option #3. The one thing you, as an object's author, could (and in fact should) do is to add your assumption to the class comment. If you add something along the lines of "I expect to be instantiated using #newWith: - if you create an instance of me using #new or #basicNew, strange things may happen", then you've done your bit and any problems occurring from inappropriate/unexpected instantiations are the responsibility of the person using your object. Trying to anticipate every possible way someone may do something weird means starting a battle you'll most likely lose. ;-) – Amos M. Carpenter Jul 26 '17 at 09:17
  • isValidInContext: anApplicationContext is very helpful indeed at the application level. – Stephan Eggermont Aug 11 '17 at 07:31
2

I agree with answer of Leandro. Defensive programming is rarely the Smalltalk way of doing things. There is no static typing for enforcing anything, nor private messages. Smalltalk is open and late-bound. But on the other hand, errors are rarely catastrophic (Smalltalk generally does not crash on error).

This late-binding along with graceful error recovery is what makes evolution of the system a very lightweight process. When you are programming in Smalltalk, you do nothing but make the live system evolve if you think of it.

Since the only thing we have is to send messages, we can eventually use that for negotiating the contracts, or just verifying some pre-condition.

The strategy that you are suggesting is possible though thru usage of Exception. The idea is that it's sometimes better to have an early Exception nearest possible to the root cause, than a late Exception more difficult to debug and correct. See for example the message #shouldNotImplement and its senders: you'll see that it is sometimes used to prevent usage of #new.

Also don't forget that there is an #initialize message that can be used to give a reasonable default value to the instance variables (a bit like the default constructor of C++). There are variants when you want to pass additional information: for Collections that are generally created thru #new: there is a #initialize: message taking the size as argument. You can refine similar mechanisms at will for passing other information at creation.

But don't try to prevent usage of #basicNew. You would create pain to yourself by breaking some services relying on this low level feature, like mutating existing instances when you modify your class layout, like copying, like storing in external files, etc... see #adoptInstance: for example.

Instead, you must learn to trust the users of your library (including yourself): users won't use basicNew unless they know what they do (they will learn that sooner or later). And as Amos said, document the contracts and expectations in class or message comments or unit tests, so that people can learn more rapidly, and maybe use not so inviting names like basicNew when you want to express that a message is for knowledgeable use only.

EDIT by the way, if you forbid basicNew, you will not be able to create instances at all, so you will have to create a new message invoking the primitive for creating an instance, and in the end you will just have obfuscated/complexified code for nothing, because you just displaced the problem. Unless you do very nasty things like:

basicNew
    thisContext sender method selector == #mySepcialCreationMessageWithParameter: ifTrue: [^super basicNew].
    ^self error: 'new instances should be created with mySepcialCreationMessageWithParameter:'

As I said above, you can play with it, but don't do it for real.

EDIT 2 Another point of view is that coding is a social activity, and the code you are writing in Smalltalk is not just for programming an automaton. It is for being read and reused by humans. In this context, defensive programming is a bit like managing by coercion. Instead of losing time in trying to constrain, it's more efficient to spend time in creating simple and logical abstractions that can be easily understood and reused, maybe in other contexts you had not foreseen.

aka.nice
  • 9,100
  • 1
  • 28
  • 40