Pergunta

Over the years I've been switching back and forward on the idea of creating Sugar Classes.

Sooner or later I find myself repeating the same operation over an existing type. Like string manipulation, date-time calculations... in these cases I am tempted to wrap the object into a custom class and get some extra syntactic sugar like:

Time.now().addDays(3).addMinutes(30).diffFrom( otherTime ).toHuman()

Most languages I know have their own I will sugarcoat this data type for you library. However given the current trend of functional style, I am questioning if sugar classes are that healthy.

Does this pattern has an official code smell name?

I want to research more on this topic, but I am having a problem to name properly the pattern.

Foi útil?

Solução

I have a whole library for lots of this stuff, especially on string operations. Often these methods are just 3-liners, but now they are just at one place.

I don't see a bad thing, because the code is reused heavily, lots of checks are in most methods I probably wouldn't bother to write, if I did write it somewhere in the application code. These helper methods have grown lots of unit tests over the years and if I discover a new edge case, I just add it in one place and not all over my application code .

TL,DR: Working like this leads to more robust, better tested code, which is easy to maintain.

Outras dicas

The problem with such classes and methods is not apparent in the individual case.

You will notice the code smell however, when you have built up a large library of them and are importing it into all your code bases just to use one or two of the functions.

Basically it comes down to a responsibility question, and it's likely that all the basic functionality that the type should reasonably be responsible for has already been incorporated into that type.

It should be quite a high bar to justify that ToHuman(), for example, belongs with the type rather than the particular model, view or project which requires the logic.

As Simon mentions, we maintain a library (actually a number of them) for this type of stuff. It's great.

In .NET for example, we have a base library which includes string manipulations, generic exceptions that seem to always be missing (BadFormatException, others). This code is highly tested and acquires a serious amount of robust-ness as the years go on. (it also makes a nice template for other platforms/languages, because while it might not be the same programming syntax, you don't really lose the oh, remember to check this).

We go so far as to include separate projects within our "framework". Examples:

Dymeng  // core, common
Dymeng.Data
Dymeng.Net
Dymeng.Web
Dymeng.Web.Mvc
Dymeng.Console  // has our args management, etc

These are all common code that we call upon from all projects. If I'm in an MVC layer, I add Dymeng.Web.Mvc and have a library of solid helpers to make things easier. In fact, I'm not sure how we'd live without it. By splitting all these little timersavers up into separate projects, we output the dlls and load only the ones needed in the working projects.

We have a system for whether these go into static or extension methods as well. For these helper methods (as opposed to helper classes), we write them as static methods. Extension methods simply call the "master" static method (similar to how we won't write logic code in an event handler method, but instead will use the event handler to call the "logical" method).

Often times I'll write a static method, then some time later I'll be working on something and think I know I've written this, where's that myString.PadLeft() extension? - at which point it's a quick jump into the lib to add the extension method for the static method that already existed.

Really, in my mind, the best thing about this though is the fact that once you get on board a system of using a common library/set of libraries like this, the code within the libraries becomes very well used and highly reliable. You know if you write another GetLineFromFile(path, lineNumber) (or whatever) you might miss something even though you've written it four times in the past three years: with a library, you know it works and you don't have to worry about it.

You adressed three different tasks levels:

Build the value for comparison:

Time.now().addDays(3).addMinutes(30)

Comparison:

.diffFrom( otherTime )

Transform to a human readable version (maybe a string??)

.toHuman()

There is no problem with a fluent interface (it's called so). But using a fluent interface will not make obsolete your responsibility to identify and separate isolated tasks.

The only problem I see is the last task ".toHuman()". This seems to be an operation that should not be part of this call chain. I would expect something like this:

.to(new HumanReadable())

where HumanReadable extends Printable.

So the responsibility what to output as a result is encapsulated and not tied to the API.

Licenciado em: CC-BY-SA com atribuição
scroll top