많은 매개 변수를 가진 단일 메소드 대 순서대로 호출해야하는 많은 메소드


16

나는 많은 일을해야하며 (이동, 회전, 특정 축을 따라 크기 조정, 최종 위치로 회전) 코드 가독성을 유지하기 위해 이것을 수행하는 가장 좋은 방법을 잘 모르겠습니다. 한편으로, 필요한 것을 수행하기 위해 많은 매개 변수 (10+)를 사용하여 단일 방법을 만들 수 있지만 이것은 악몽을 읽는 코드입니다. 반면에 각각 1-3 개의 매개 변수로 여러 메서드를 만들 수 있지만 올바른 결과를 얻으려면 이러한 메서드를 매우 구체적인 순서로 호출해야합니다. 나는 메소드가 한 가지 일을하고 잘하는 것이 가장 좋다는 것을 읽었지만, 찾기 어려운 버그에 대한 코드를 열기 위해 호출 해야하는 많은 메소드가있는 것처럼 보입니다.

버그를 최소화하고 코드를 쉽게 읽을 수있는 프로그래밍 패러다임이 있습니까?


3
가장 큰 문제는 '순서대로 호출하지 않는 것'이 아니라, (또는 미래의 프로그래머가) 순서대로 호출해야한다는 것을 알지 못하는 것입니다. 유지 보수 프로그래머가 세부 사항을 알고 있는지 확인하십시오 (요구 사항, 설계 및 스펙을 문서화하는 방법에 크게 좌우 됨). 단위 테스트, 주석 사용 및 모든 매개 변수를 사용하고 다른 매개 변수를 호출하는 도우미 기능 제공
mattnz

그냥 오프 손 발언으로, 유창 인터페이스명령 패턴이 유용 할 수 있습니다. 그러나 어떤 디자인이 가장 적합한 지 결정하는 것은 사용자 (소유자)와 라이브러리 사용자 (고객)의 책임입니다. 다른 사람들이 지적했듯이, 사용자는 조작이 비순환 적 이며 (실행 순서에 민감 함) 사용자에게 올바르게 사용하는 방법을 찾지 못하면 의사 소통 해야합니다.
rwong

계산 불가능한 연산의 예 : 이미지 변환 (회전, 크기 조절 및 자르기), 행렬 곱셈 등
rwong

어쩌면 카레를 사용할 수 있습니다. 이렇게하면 메소드 / 함수를 잘못된 순서로 적용하는 것이 불가능합니다.
Giorgio

여기서 어떤 방법을 사용하고 있습니까? 내 말은, 표준은 그것을 적용하는 메소드 에 전달하는 변형 객체 (예 : Java의 Affine Transform for 2D 물건)를 전달하는 것입니다. 변환의 내용은 설계에 따라 초기 작업을 호출하는 순서에 따라 다릅니다 (따라서 "원하는 순서로"가 아니라 "필요한 순서로 호출합니다").
Clockwork-Muse

답변:


24

시간적 커플 링에 주의하십시오 . 그러나 이것이 항상 문제가되는 것은 아닙니다.

순서대로 단계를 수행해야하는 경우 1 단계는 2 단계에 필요한 일부 오브젝트 (예 : 파일 스트림 또는 기타 데이터 구조)를 생성합니다. 이 혼자 두 번째 기능이 있어야 한다 이 실수로 잘못된 순서로 호출도 가능하지, 처음으로 불리.

기능을 한 입 크기로 분할하면 각 부분을 이해하기 쉽고 테스트하기가 훨씬 쉽습니다. 100 줄거대한 함수 와 중간 부분에 무언가 가 있다면 실패한 테스트에서 무엇이 잘못되었는지 어떻게 알 수 있습니까? 5 가지 라인 방법 중 하나가 중단되면 실패한 단위 테스트에서주의가 필요한 하나의 코드로 즉시 지시합니다.

복잡한 코드 다음과 같습니다.

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

원시 데이터를 완성 된 위젯으로 변환하는 프로세스 중 어느 시점에서나 각 함수는 프로세스의 다음 단계에서 필요한 것을 반환합니다. 슬래그에서 합금을 형성 할 수 없으며, 먼저 슬래그를 제련 (정제)해야합니다. 입력으로 적절한 허용 (예 : 강철)이 없으면 위젯을 만들 수 없습니다.

각 단계의 특정 세부 사항은 테스트 할 수있는 개별 기능에 포함되어 있습니다. 마이닝 암석의 전체 프로세스를 단위 테스트하고 위젯을 만드는 것이 아니라 각 특정 단계를 테스트하십시오. 이제 "위젯 작성"프로세스가 실패하는 경우 특정 이유를 좁힐 수있는 쉬운 방법이 있습니다.

테스트 및 정확성 증명의 이점 외에도 이러한 방식으로 코드를 작성하는 것이 훨씬 쉽습니다. 아무도 거대한 매개 변수 목록을 이해할 수 없습니다 . 작은 조각으로 분해, 및 각 작은 조각 수단을 보여입니다 grokkable .


2
감사합니다. 이것이 문제를 해결하는 좋은 방법이라고 생각합니다. 객체의 수를 늘리고 (불필요하다고 느낄 수도 있지만) 가독성을 유지하면서 순서를 강제합니다.
tomsrobots

10

"모든 명령을 순서대로 실행해야합니다"라는 인수는 무의미합니다. 거의 모든 코드가 올바른 순서로 실행되어야하기 때문입니다. 결국, 파일에 쓸 수 없다가 파일을 연 다음 닫을 수 없습니까?

코드를 가장 유지 관리하기 쉽게 만드는 것에 집중해야합니다. 이것은 일반적으로 작고 쉽게 이해되는 함수 작성을 의미합니다. 각 기능은 단일 목적을 가져야하며 예상치 못한 부작용이 없어야합니다.


5

» ImageProcesssor «(또는 프로젝트에 적합한 이름) 및 구성 객체 ProcessConfiguration을 작성합니다. 필요한 모든 매개 변수를 보유 합니다.

 ImageProcessor p = new ImageProcessor();

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

이미지 프로세서 내부에서 하나의 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
    }
}

이 방법은 올바른 순서로 변형 메소드를 호출 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은 그러한 파이프 라인을 인스턴스화하고 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

어떤 종류의 카레 사용을 고려 했습니까 ? 클래스 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
    }
}

이제 클래스를 대체 할 수있는 Processor두 개의 클래스로 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메소드가 정확히 하나의 인수 를 가질 필요는 없습니다 .


유일한 아이디어는;) 유일한 차이점은 파이프 라인 이 엄격한 처리 순서를 시행 한다는 것 입니다.
토마스 정크

@ThomasJunk : 내가 이해하는 한,이 요구 사항은 "올바른 결과를 얻으려면 이러한 방법을 매우 특정한 순서로 호출해야합니다." 엄격한 실행 순서를 갖는 것은 함수 구성과 매우 흡사합니다.
Giorgio

그러나 주문 순서가 변경되면 많은 리팩토링을 수행해야합니다.)
Thomas Junk

@ThomasJunk : 맞습니다. 실제로 응용 프로그램에 따라 다릅니다. 처리 단계를 매우 자주 교체 할 수 있다면 아마도 더 나은 방법 일 것입니다.
Giorgio
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.