Required Attributes in Smalltalk

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 to instantiate and then suggest using a class create method. But any user knows that new or basicNew can be used at any time.

I was thinking of throwing new and basicNew to throw an exception, but this seems to be too harsh a dimension as sometimes I may need to instantiate for debugging, for example.

Is there another library or mechanism to enforce these specific attributes?

+3


source to share


2 answers


Not. And this is good.

Here are some approaches you could follow:



  • Make sure your objects are valid by providing validation for them. This is a very broad topic, so I'll just say that a validation framework is the ability to examine your objects and somehow make it explicit for any rules they cannot enforce.

    Depending on what you are doing, you can limit the check to the GUI when objects are born or modified. However, a more sophisticated approach is to allow any object to decide whether it is valid or not (in this context).

  • Exclude certain settings from the protocol on the instance side. Provide only a few sets of keywords that will fail if something is missing or irrelevant. This means that you would provide methods such as Point >> #x:y:

    , but not Poit >> #x:

    or Point >> #y:

    .

    As the example shows, it will be difficult for you to fully embrace this practice because the base classes do not follow this style. Also note that this practice will require some checking, because checking for only is notNil

    usually too naive.

  • Relax and do nothing until the need arises to address these problems.

    In other words, put off the problem until your software is mature enough to draw attention to the way you create and modify its objects.

The reason I claim that everything is fine as it is is because Smalltalk, which traditionally promoted openness of security, encouraged and even encouraged people to experiment even in the "wrong" way. Every function aimed at preventing the violation of objects will sooner or later force you not to repair them. More importantly, they will consume a lot of energy that could otherwise be applied to more productive goals.

+8


source


I agree with Leandro's answer. Defensive programming is rarely the Smalltalk way of doing things. There is no static typing to enforce anything, nor private messages. Smalltalk is open source and late. But on the other hand, errors are rarely catastrophic (Smalltalk usually doesn't crash on error).

This late binding, along with graceful error recovery, is what makes system evolution a very easy process. When you program in Smalltalk, you are not doing anything, but you are making a living system grow if you think about it.

Since the only thing we have is to send messages, we may end up using this to negotiate contracts or just check a precondition.

The strategy you propose is possible, though through the use of Exceptions. The idea is that sometimes it is better to have an early exception that is as large as possible for the root cause than a late exception, more difficult to debug and fix. See for example a message #shouldNotImplement

and its senders: you will see that it is sometimes used to prevent use #new

.

Also don't forget that there is a post #initialize

that can be used to provide a sensible default for instance variables (a bit like the default constructor for C ++). There are options when you want to pass additional information: for collections that are usually created via #new:

, there is a message #initialize:

that takes a size as an argument. You can enhance such mechanisms as you wish to convey other information during creation.

But don't try to prevent use #basicNew

. You would be creating pain for yourself by breaking up some services relying on this low level feature, like mutating existing instances when the layout of your class changes, like copying like saving to external files, etc ... like #adoptInstance:

.



Instead, you must learn to trust the users of your library (including yourself): users won't use basicNew

unless they know what they are doing (they will find out that sooner or later). And as Amos said, document contracts and expectations in class comments or comments or unit tests so people can learn faster and perhaps use less appealing names like basicNew when you want to express that the message is for knowledgeable use only. ...

EDIT if you disallow basicNew you won't be able to instantiate at all, so you'll have to create a new message calling the primitive to instantiate, and in the end you'll just be confused / stacked up for nothing, because you're just biased by the problem. Unless you are doing 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, you can play with it, but don't really do it.

EDIT 2 Another point is that coding is a social activity, and the code you write in Smalltalk is not just for automaton programming. It is designed to be read and reused by humans. In this context, defensive programming is a bit like duress management. Instead of wasting time trying to contain it, it is more efficient to spend time creating simple and logical abstractions that can be easily understood and reused, perhaps in other contexts that you did not foresee.

+2


source







All Articles