16

I have some raw data I need to do many things to (shift it, rotate it, scale it along certain axis, rotate it to a final position) and I am not sure what the best way to do this to maintain code readability. On one hand, I can make a single method with many parameters (10+) to do what I need, but this is a code reading nightmare. On the other hand, I could make multiple methods with 1-3 parameters each, but these methods would need to be called in a very specific order to get the correct result. I have read that it is best for methods to do one thing and do it well, but it seems like having many methods that need to be called in order opens up code for hard-to-find bugs.

Is there a programming paradigm I could use that would minimize bugs and make code easier to read?

  • 3
    The biggest problem is not 'not calling them in order', its 'not knowing' that you (or more precisely, a future programmer) must call them in order. Make sure any maintenance programmer knows the details (This will largely depend on how you document requirements, design and specifications). Use Unit tests, comments and provide helper functions which take all the parameters and call the others – mattnz Mar 1 '15 at 2:24
  • Just as an off-hand remark, fluent interface and command pattern might be useful. However, it is up to you (as the proprietor) and your library's users (the customers) to decide what design is the best. As others point out, it is necessary to communicate to the users that the operations are non-commutative (that they are sensitive to the order of execution), without which your users would never find out how to use them correctly. – rwong Mar 1 '15 at 4:17
  • Examples of non-commutative operations: image transformations (rotation, scaling and cropping), matrix multiplications, etc. – rwong Mar 1 '15 at 4:18
  • Maybe you can use currying: this would make it impossible to apply the methods / functions in the wrong order. – Giorgio Mar 1 '15 at 9:06
  • What set of methods are you working on here? I mean, I think the standard is to pass a transformation object (like Java's Affine Transform for 2D stuff) that you pass to some method that applies it. The contents of the transform are different depending on the order you call the initial operations on it, by design (so it's "you call it in the order you need it", not "in the order I want it"). – Clockwork-Muse Mar 1 '15 at 9:14
23

Beware of temporal coupling. However, this is not always an issue.

If you must perform steps in order, it follows that step 1 produces some object required for step 2 (e.g. a file stream or other data structure). This alone requires that the second function must be called after the first, it is not even possible to call them in the wrong order accidentally.

By splitting your functionality up into bite-sized pieces, each part is easier to understand, and definitely easier to test in isolation. If you have a huge 100 line function and something in the middle breaks, how does your failed test tell you what is wrong? If one of your five line methods breaks, your failed unit test directs you immediately toward the one piece of code that needs attention.

This is how complex code should look:

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

At any point during the process of converting raw data into a finished widget, each function returns something required by the next step in the process. One cannot form an alloy from slag, one must smelt (purify) it first. One may not create a widget without the proper allow (e.g. steel) as input.

The specific details of each step are contained in individual functions that can be tested: rather than unit testing the entire process of mining rocks and creating widgets, test each specific step. Now you have an easy way of ensuring that if your "create widget" process fails, you can narrow down the specific reason.

Aside from the benefits of testing and proving correctness, writing code this way is far easier to read. Nobody can understand a huge parameter list. Break it down into small pieces, and show what each little piece means: that is grokkable.

  • 2
    Thanks, I think this is a good way to tackle the problem. Even though it increases the number of objects (and that might feel unnecessary), it forces order while maintaining legibility. – tomsrobots Mar 1 '15 at 14:56
10

The "must be executed in order" argument is moot since pretty much all of your code must be executed in the correct order. After all, you can't write to a file, then open it then close it, can you?

You should concentrate on what makes your code the most maintainable. This will usually mean writing functions that are small and easily understood. Each function should have a single purpose and not have side effects that are unanticipated.

5

I would create an »ImageProcesssor« (or whatever name suits your project) and a configuration object ProcessConfiguration, which holds all necessary parameters.

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

Inside the imageprocessor you encapsulate the whole process behind one mehtod process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

This method calls the transformational methods in the correct order shift(), rotate(). Each method gets appropriate parameters from the passed ProcessConfiguration.

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

I used fluid interfaces

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

which allows nifty initialization (as seen above).

The obvious advantage, encapsulating necessary parameters in one object. Your method signatures become readable:

private void shift(Image i, ProcessConfiguration c)

It is about shifting an image and detailed parameters are somehow configured.

Alternatively, you could create a ProcessingPipeline:

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

A method call to a method processImage would instantiate such a pipeline and make transparent what and in which order is done: shift, rotate

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}
3

Have you considered using some kind of currying? Imagine that you have a class Processee and a class Processor:

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

Now you can replace class Processor by two classes Processor1 and Processor2:

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

You can then call the operations in the right order using:

new Processor1(processee).process(a1).process(a2);

You can apply this pattern multiple times if you have more than two parameters. You can also group the arguments as you want, i.e. you do not need to have each process method take exactly one argument.

  • We had nearly the same idea ;) The only difference is, that your Pipeline enforces a strict processing order. – Thomas Junk Mar 1 '15 at 9:35
  • @ThomasJunk: As far as I understood, this is a requirement: "these methods would need to be called in a very specific order to get the correct result". Having a strict execution order sounds very much like function composition. – Giorgio Mar 1 '15 at 10:10
  • And so did I. But, if processing order changes, you have to do a lot of refactoring ;) – Thomas Junk Mar 1 '15 at 10:19
  • @ThomasJunk: True. It really depends on the application. If the processing steps can be swapped very often, then probably your approach is better. – Giorgio Mar 1 '15 at 10:44

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.