具有多个参数的单个方法与必须按顺序调用的许多方法


16

我有一些原始数据,我需要做很多事情(移动它,旋转它,沿某个轴缩放它,将它旋转到一个最终位置),而且我不确定保持代码可读性的最佳方法是什么。一方面,我可以使用具有多个参数(10个以上)的单个方法来执行我需要的操作,但这是一场代码阅读的噩梦。另一方面,我可以使用1-3个参数制作多个方法,但是这些方法需要以非常特定的顺序调用才能获得正确的结果。我已经读过,最好的方法是做一件事情并且做好事,但是似乎有很多需要调用的方法才能打开代码以查找难以发现的错误。

我是否可以使用一种编程范例来使错误最小化并使代码更易于阅读?


3
最大的问题不是“不按顺序调用”,而是“不知道”您(或更准确地说,未来的程序员)必须按顺序调用它们。确保任何维护程序员都知道细节(这在很大程度上取决于您记录需求,设计和规格的方式)。使用单元测试,注释并提供采用所有参数并调用其他参数的辅助函数
mattnz 2015年

就像不客气的话一样,流畅的界面命令模式可能会很有用。但是,由您(作为所有者)和图书馆的用户(客户)来决定哪种设计是最佳的。正如其他人指出的那样,有必要向用户传达这些操作是不可交换的(它们对执行顺序很敏感),否则用户将永远找不到正确使用它们的方法。
rwong 2015年

非交换运算的示例:图像变换(旋转,缩放和裁剪),矩阵乘法等
rwong 2015年

也许您可以使用currying:这将导致不可能以错误的顺序应用方法/函数。
乔治

您在这里采用哪种方法?我的意思是,我认为标准是要传递一个转换对象(例如Java的2D 仿射转换),然后将其传递给某种应用它的方法。转换的内容根据设计上调用初始操作的顺序而有所不同(因此,它是“按需要的顺序调用它”,而不是“按我想要的顺序”)。
Clockwork-Muse

Answers:


24

当心时间耦合。但是,这并不总是一个问题。

如果必须按顺序执行步骤,则步骤1会生成步骤2所需的某些对象(例如,文件流或其他数据结构)。仅此一项就要求必须在第一个函数之后调用第二个函数,甚至不可能意外地以错误的顺序调用它们。

通过将您的功能分成几小部分,每个部分都更易于理解,并且绝对更容易进行隔离测试。如果您有一个巨大的100行函数,并且中间出现中断,那么您的失败测试如何告诉您哪里出了问题?如果您的五行方法之一中断,则失败的单元测试会将您立即引导至需要注意的一段代码。

这是复杂的代码如何应该看看:

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;
  }
}

在将原始数据转换为完成的小部件的过程中的任何时候,每个函数都会返回该过程的下一步所需的内容。一个人不能从炉渣中形成合金,必须先将其熔炼(提纯)。如果没有适当的允许(例如钢)作为输入,则可能无法创建小部件。

每个步骤的具体细节包含在可以测试的各个功能中:测试每个特定步骤,而不是对采矿岩石和创建小部件的整个过程进行单元测试,而不是对其进行单元测试。现在,您有一种简单的方法来确保如果“创建窗口小部件”过程失败,则可以缩小特定原因的范围。

除了测试和证明正确性的好处外,以这种方式编写代码更容易阅读。没有人能理解庞大的参数列表。将其分解成小块,并显示每个小块的含义:这很古怪


2
谢谢,我认为这是解决问题的好方法。即使它增加了对象的数量(并且可能觉得没有必要),但它在保持可读性的同时强制了顺序。
tomsrobots

10

因为几乎所有代码都必须以正确的顺序执行,所以“必须按顺序执行”参数很无聊。毕竟,您不能写入文件,然后打开然后关闭它,可以吗?

您应该集中精力使代码最易于维护。这通常意味着编写小的函数并且易于理解。每个功能应具有单一目的,并且不应具有无法预料的副作用。


5

我将创建一个» ImageProcesssor «(或任何适合您项目的名称)和一个配置对象ProcessConfiguration,其中包含所有必要的参数。

 ImageProcessor p = new ImageProcessor();

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

在图像处理器内部,将整个过程封装在一个方法后面 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
    }
}

该方法调用正确的顺序变革方法shift()rotate()。每个方法都从传递的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;

}

我使用了流体接口

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

允许进行漂亮的初始化(如上所示)。

明显的优势是将必要的参数封装在一个对象中。您的方法签名变得可读:

private void shift(Image i, ProcessConfiguration c)

它是关于移动图像和详细参数以某种方式配置

或者,您可以创建一个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
    }

}

对方法的方法调用processImage将实例化这样的管道,并使执行的顺序和顺序透明:shiftrotation

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

您是否考虑过使用某种欺骗手段?假设您有一个班级Processee和一个班级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
    }
}

现在您可以将class替换Processor为两个class Processor1Processor2

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
    }
}

然后,您可以使用以下命令以正确的顺序调用操作:

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

如果您有两个以上的参数,则可以多次应用此模式。您也可以根据需要对参数进行分组,即,您不必使每个process方法都采用一个参数。


我们有几乎相同的想法;)唯一的区别是,您的管道执行严格的处理顺序。
Thomas Junk 2015年

@ThomasJunk:据我了解,这是一个要求:“这些方法需要以非常特定的顺序调用才能获得正确的结果”。具有严格的执行顺序听起来很像函数组成。
Giorgio'Mar

我也是。但是,如果处理顺序发生变化,则必须进行大量的重构;)
Thomas Junk

@ThomasJunk:是的。它确实取决于应用程序。如果处理步骤可以经常交换,那么您的方法可能更好。
乔治
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.