3

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.

  • 9
    This is called "fluent interface" and it is generally considered a good thing. Also, it is much better from functional standpoint, as it allows changes to be made to immutable structure. – Euphoric Jun 25 '17 at 6:44
  • Just use extension methods, if your language supports them. – CodesInChaos Jun 25 '17 at 9:46
  • 2
    I like helper classes just fine so long as they don't end up in folders or classes named misc. – candied_orange Jun 25 '17 at 12:57
  • @CandiedOrange Or in a folder named "Support", another cannot-be-bothered-to-put-this-where-it-belongs-bag. If I create a helper class with static functions for some specific subdomain, I tend to call it "<SomeSubdomain>Monkey". – Martin Maat Jun 25 '17 at 15:35
  • 1
    @CandiedOrange, if the static method is pure, then its only dependencies are those injected into it via its parameters. Thus there is no DIP violation. Quite the opposite; all dependencies are injected. Problems only arise when those methods have global dependencies and/or have side effects. Then they should not be static. – David Arno Jun 26 '17 at 10:31
7

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.

  • but do you code those methods as independent static methods as in compare(str_1, str_2) or as members of a wrapper(String) class as new SugarString("my string").compare("other") ??? – SystematicFrank Jun 26 '17 at 16:49
  • Usually I use independent static methods, because I find them more flexible. – Simon Jun 26 '17 at 17:01
  • Right on. We use a number of projects in .NET, each that covers a specific namespace so we can load only the relevant libraries. The base lib has all sorts of this stuff, and anything that goes in here gets heavily tested and acquires some serious robust-ness over the years. Regarding static vs extension, we often do both... there's a static method (aka, the master), and if I find myself in code thinking where's that myString.PadLeft() extension? I just hop into the lib and add an extension that points to the static method. – jleach Jul 8 '17 at 12:59
4

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.

1

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.

0

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.

  • 3
    Actually I think the problem with toHuman() is that it's name failed to give me any idea what it does beyond what toString() should do already. – candied_orange Jun 25 '17 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.