코딩 스타일 문제 : 매개 변수를 가져 와서 수정 한 다음 해당 매개 변수를 반환하는 함수가 있어야합니까?


19

나는이 두 가지 관행이 같은 동전의 양면인지 아니면 어느 쪽이 더 좋은지에 대해 친구와 약간의 논쟁을 벌이고 있습니다.

매개 변수를 가져 와서 멤버를 채우고 반환하는 함수가 있습니다.

Item predictPrice(Item item)

전달 된 동일한 객체 에서 작동 하므로 항목을 반환 할 필요가 없다고 생각합니다. 실제로, 발신자의 관점에서 볼 때, 항목 을 반환 하지 않을 것으로 예상 할 수 있으므로 문제를 혼동 합니다.

그는 그것이 아무런 차이가 없다고 주장하며, 새로운 아이템을 만들어서 그것을 반환하더라도 상관 없습니다. 다음과 같은 이유로 동의하지 않습니다.

  • 항목에 대한 여러 참조가 (또는 포인터 또는 기타) 전달되는 경우 새 객체를 할당하고 반환하는 것은 참조가 올바르지 않기 때문에 중요합니다.

  • 비 메모리 관리 언어에서 새 인스턴스를 할당하는 함수는 메모리의 소유권을 주장하므로 어느 시점에서 호출되는 정리 메소드를 구현해야합니다.

  • 힙에 할당하면 비용이 많이들 수 있으므로 호출 된 함수가이를 수행하는지 여부가 중요합니다.

따라서 객체를 수정하거나 새 객체를 할당하는지 여부를 메소드 서명을 통해 볼 수있는 것이 매우 중요하다고 생각합니다. 결과적으로 함수가 전달 된 객체를 수정하기 때문에 서명은 다음과 같아야합니다.

void predictPrice(Item item)

내가 작업 한 모든 코드베이스 (우리가 작업하는 언어 인 Java가 아닌 C 및 C ++ 코드베이스)에서 위의 스타일은 본질적으로 훨씬 더 숙련 된 프로그래머가 준수하고 유지했습니다. 그는 코드베이스와 동료의 샘플 크기가 가능한 모든 코드베이스와 동료 중에서 작기 때문에 내 경험이 하나의 우수성에 대한 진정한 지표는 아니라고 주장합니다.

그래서 어떤 생각?


strcat은 매개 변수를 수정하고 여전히 반환합니다. strcat이 void를 반환해야합니까?
Jerry Jeremiah

Java와 C ++는 매개 변수 전달과 관련하여 매우 다른 동작을합니다. Itemclass Item ...아니고 그렇지 않은 경우 typedef ...& Item현지인 item은 이미 사본입니다
Caleth

google.github.io/styleguide/cppguide.html#Output_Parameters Google은 출력에 RETURN 값 사용을 선호합니다
Firegun

답변:


26

이것은 실제로 의견의 문제이지만 가치가있는 것으로 항목이 수정 된 경우 수정 된 항목을 반환하는 것이 잘못되었습니다. 별도로, predictPrice항목을 수정하려는 경우 항목을 수행 할 것임을 나타내는 이름이 있어야합니다 (예 : 이와 유사한 것 setPredictPrice).

나는 (순서대로) 선호합니다

  1. 그것은 (모듈러스가 아닌 전체적인 디자인에있을 수있는 좋은 이유) predictPrice방법Item 이었습니다.

    • 예상 가격을 반환하거나

    • setPredictedPrice대신 같은 이름을 가졌다

  2. predictPrice 않았다 수정 Item하지만, 예상 가격을 반환

  3. predictPrice이었다 void라는 방법setPredictedPrice

  4. 즉, predictPrice반환 this(그것의 일부 어떤 경우에 다른 방법으로 방법 체인에 대한) (그리고 불렀다 setPredictedPrice)


1
답장을 보내 주셔서 감사합니다. 1과 관련하여 Item 내부에 predictPrice를 가질 수 없습니다. 2는 좋은 제안입니다. 아마 이것이 올바른 해결책이라고 생각합니다.
Squimmy

2
@Squimmy : 그리고 과연, 방금 사용하는 사람에 의해 발생하는 버그를 다루는 15분 보냈다 정확히 : 당신에 대해 주장하고있는 패턴을 a = doSomethingWith(b) 수정 b 하고 그것이 무엇인지 알고 있다고 생각 나중에 내 코드를 날려 수정, 그것을 반환 b했다을 그 안에. :-)
TJ Crowder

11

객체 지향 디자인 패러다임 내에서 객체 외부의 객체를 수정해서는 안됩니다. 객체 상태에 대한 모든 변경은 객체의 메소드를 통해 수행 해야 합니다.

따라서 void predictPrice(Item item)다른 클래스의 멤버 함수로는 잘못되었습니다 . C 시대에는 받아 들여질 수 있었지만 Java 및 C ++의 경우 객체를 수정하면 객체와 더 긴밀하게 연결되어 다른 설계 문제가 발생할 수 있습니다 (클래스를 리팩터링하고 필드를 변경할 때, 이제 다른 파일에서 'predictPrice'를 변경해야합니다.

새 객체를 반환하고 관련 부작용이없는 경우 전달 된 매개 변수는 변경되지 않습니다. 귀하 (predictPrice 방법)는 해당 매개 변수가 사용되는 곳을 모릅니다. 가 Item해시 곳의 열쇠는? 이 작업에서 해시 코드를 변경 했습니까? 다른 사람이 변하지 않기를 기대하고 있습니까?

이 디자인 문제는 객체를 수정해서는 안된다는 것을 강력하게 암시하는 것입니다 (많은 경우 불변성을 주장합니다). 그렇다면 상태 변경 사항이 객체가 아닌 객체 자체에 의해 포함되고 제어되어야합니다 수업 이외의 다른.


해시에서 무언가의 필드를 피들 링하면 어떻게되는지 봅시다. 코드를 보자.

import java.util.*;

public class Main {
    public static void main (String[] args) {
        Set set = new HashSet();
        Data d = new Data(1,"foo");
        set.add(d);
        set.add(new Data(2,"bar"));
        System.out.println(set.contains(d));
        d.field1 = 2;
        System.out.println(set.contains(d));
    }

    public static class Data {
        int field1;
        String field2;

        Data(int f1, String f2) {
            field1 = f1;
            field2 = f2;
        }

        public int hashCode() {
            return field2.hashCode() + field1;
        }

        public boolean equals(Object o) {
            if(!(o instanceof Data)) return false;
            Data od = (Data)o;
            return od.field1 == this.field1 && od.field2.equals(this.field2);

        }
    }
}

이데온

그리고 이것이 가장 큰 코드는 아니지만 (직접 필드에 액세스), 가변 데이터가 HashMap의 키로 사용 되거나이 경우에는 해시 세트.

이 코드의 출력은 다음과 같습니다.

true
false

일어난 일은 해시 코드가 삽입되었을 때 사용 된 객체가 해시에있는 곳입니다. hashCode를 계산하는 데 사용되는 값을 변경해도 해시 자체는 다시 계산되지 않습니다. 이 퍼팅의 위험이 있습니다 어떤 해시에 열쇠로 변경 가능한 객체를.

따라서 질문의 원래 측면으로 돌아가서 호출되는 메소드는 매개 변수로 가져 오는 오브젝트가 어떻게 사용되고 있는지 "알지"않습니다. 이를 수행 할 수있는 유일한 방법으로 "객체를 변형시키다"를 제공한다는 것은 크롤링 할 수있는 여러 가지 미묘한 버그가 있음을 의미합니다. 해시에서 값을 잃는 것처럼 ... , 그건 나쁜 버그입니다 (해시 맵에 20 개의 항목을 더 추가 한 다음 갑자기 다시 나타날 때까지 값을 잃었습니다).

  • 가없는 아주 새로운 객체가 할 수있는 가장 안전한 일이 반환 객체를 변경하는 이유는.
  • 객체를 수정해야 할 적절한 이유 있을 경우 필드를 뒤틀 수있는 외부 함수가 아닌 객체 자체 (메서드 호출)를 통해 수정해야합니다.
    • 이를 통해 유지 관리 비용을 낮추면서 객체를 리팩터링 할 수 있습니다.
    • 이 객체는 해시 코드를 계산 값이 있는지 확인 할 수 있습니다 하지 않는 변화를 (또는 해시 계산의 일부가 아닌)

관련 : if 문의 조건문으로 사용 된 인수의 값을 겹쳐 쓰고 리턴하는 경우 동일한 if 문 내부


3
소리가 잘 들리지 않습니다. 대부분의 경우 predictPrice에 직접 바이올린 안 Item'의 회원,하지만 방법 호출에 문제가 있습니다 Item그렇게 그렇게? 그것이 수정되는 유일한 객체라면, 아마도 수정중인 객체의 방법이어야한다고 주장 할 수 있지만, 다른 경우에는 여러 객체 (같은 클래스가 아니어야 함)가 특히 수정되고 있습니다 오히려 높은 수준의 방법.

@delnan 나는 이것이 해시에서 사라지고 다시 나타나는 값 중 하나를 추적해야했던 버그에 대한 (과도한) 반응 일 수 있음을 인정할 것입니다. 사용하기 위해 객체의 복사본을 만드는 것이 아닙니다. 취할 수있는 방어적인 프로그래밍 방법 (예 : 불변 개체)이 있습니다. 그러나 개체의 "소유자"가 아니거나 개체 자체가 쉽게 물릴 수있는 개체가 아닌 경우 개체 자체에서 값을 다시 계산하는 것과 같은 것들 너 섰어.

알다시피, 캡슐화를 향상시키기 위해 클래스 함수 대신 더 많은 무료 함수를 사용하는 것에 대한 좋은 주장이 있습니다. 당연히, 상수 객체를 얻는 함수 (이것이나 매개 변수에 관계없이)는 관찰 가능한 방식으로 변경해서는 안됩니다 (일부 상수 객체는 캐시 할 수 있음).
중복 제거기

2

나는 항상 다른 사람의 대상을 변경하지 않는 관행을 따릅니다 . 즉, 클래스 / 구조체 / 소유 한 객체 만 돌연변이시킵니다.

다음과 같은 데이터 클래스가 제공됩니다.

class Item {
    public double price = 0;
}

괜찮습니다 :

class Foo {
    private Item bar = new Item();

    public void predictPrice() {
        bar.price = bar.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
foo.predictPrice();
System.out.println(foo.bar.price);
// Since I called predictPrice on Foo, which takes and returns nothing, it's
// apparent something might've changed

그리고 이것은 매우 분명합니다.

class Foo {
    private Item bar = new Item();
}
class Baz {
    public double predictPrice(Item item) {
        return item.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
Baz baz = Baz();
foo.bar.price = baz.predictPrice(foo.bar);
System.out.println(foo.bar.price);
// Since I explicitly set foo.bar.price to the return value of predictPrice, it's
// obvious foo.bar.price might've changed

그러나 이것은 내 취향에 비해 너무 진흙 투성이이며 신비합니다.

class Foo {
    private Item bar = new Item();
}
class Baz {
    public void predictPrice(Item item) {
        item.price = item.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
Baz baz = Baz();
baz.predictPrice(foo.bar);
System.out.println(foo.bar.price);
// In my head, it doesn't appear that to foo, bar, or price changed. It seems to
// me that, if anything, I've placed a predicted price into baz that's based on
// the value of foo.bar.price

미래에 더 나은 답변을 작성하는 방법을 알 수 있도록 의견과 함께 모든 공감대와 함께하십시오 :)
Ben Leggiero

1

구문은 언어마다 다르며 언어에 따라 다르지만 완전히 다른 언어를 의미하기 때문에 언어에 국한되지 않은 코드를 표시하는 것은 의미가 없습니다.

Java에서 Item참조 유형은 인스턴스의 객체에 대한 포인터 유형입니다 Item. C ++에서이 유형은 다음과 같이 작성됩니다 Item *(언어마다 같은 구문이 다릅니다). 따라서 Item predictPrice(Item item)Java Item *predictPrice(Item *item)에서는 C ++ 과 같습니다 . 두 경우 모두 객체에 대한 포인터를 가져 와서 객체에 대한 포인터를 반환하는 함수 (또는 메서드)입니다.

C ++에서 Item(다른 것은 제외)는 객체 유형입니다 (가상 Item이름 이라고 가정 ). 이 유형의 값은 객체 "객체"이며 객체 Item predictPrice(Item item)를 가져 와서 객체를 반환하는 함수를 선언합니다. 이후 &사용되지 않습니다, 그것은 통과 값으로 반환됩니다. 즉, 객체가 전달되면 복사되고 반환되면 복사됩니다. Java에는 오브젝트 유형이 없기 때문에 Java에는 해당 항목이 없습니다.

그래서 어느 것입니까? 질문에서 묻는 많은 것들 (예 : "전달 된 동일한 객체에서 작동하기 때문에 ...")은 여기에서 전달 및 반환되는 내용을 정확히 이해하는 데 달려 있습니다.


1

코드베이스가 준수하는 규칙은 구문에서 분명한 스타일을 선호하는 것입니다. 함수가 값을 변경하면 포인터로 전달하는 것을 선호하십시오.

void predictPrice(Item * const item);

이 스타일의 결과는 다음과 같습니다.

  • 그것을 호출하면 다음을 볼 수 있습니다.

    predictPrice (& my_item);

스타일에 따라 수정 될 것입니다.

  • 그렇지 않으면 함수가 인스턴스를 수정하지 못하게하려는 경우 const ref 또는 value by pass를 선호하십시오.

구문이 훨씬 명확하지만 Java에서는 사용할 수 없습니다.
벤 레지에로

0

나는 선호도가 높지 않지만 객체를 반환하면 API의 유연성이 향상된다고 투표합니다.

메소드가 다른 오브젝트를 리턴 할 자유가있는 동안에 만 의미가 있습니다. javadoc이 말하면 @returns the same value of parameter a쓸모가 없습니다. 그러나 javadoc이라고 말하면 @return an instance that holds the same data than parameter a동일한 인스턴스 또는 다른 인스턴스를 반환하는 것이 구현 세부 사항입니다.

예를 들어, 현재 프로젝트 (Java EE)에는 비즈니스 계층이 있습니다. 직원, 예를 들어 직원과 같은 직원을 DB에 저장하고 자동 ID를 할당합니다.

 public Employee createEmployee(Employee employeeData) {
   this.entityManager.persist(employeeData);
   return this.employeeData;
 }

물론 JPA가 Id를 할당 한 후 동일한 객체를 반환하므로이 구현과 차이가 없습니다. 이제 JDBC로 전환하면

 public Employee createEmployee(Employee employeeData) {
   PreparedStatement ps = this.connection.prepareStatement("INSERT INTO EMPLOYEE(name, surname, data) VALUES(?, ?, ?)");
   ... // set parameters
   ps.execute();
   int id = getIdFromGeneratedKeys(ps);
   ??????
 }

이제 ???? 세 가지 옵션이 있습니다.

  1. Id에 대한 setter를 정의하면 동일한 객체를 반환합니다. 미운, 왜냐하면 setId어디에서나 사용할 수 있기 때문 입니다.

  2. 무거운 반사 작업을 수행하고 Employee객체에 ID를 설정하십시오 . 더럽지 만 적어도 createEmployee레코드 로 제한됩니다 .

  3. 원본을 복사하고 설정을 Employee허용 하는 생성자를 제공하십시오 id.

  4. DB에서 오브젝트를 검색하십시오 (DB에서 일부 필드 값이 계산되거나 영역을 채우려는 경우 필요할 수 있음).

이제 1과 2의 경우 동일한 인스턴스를 반환 할 수 있지만 3과 4의 경우 새 인스턴스를 반환합니다. 프린시 페에서 API로 "실제"값이 메소드에 의해 리턴 된 값이 될 수 있다고 선언 한 경우 더 많은 자유가 있습니다.

내가 생각할 수있는 유일한 실제 주장은 구현 1 또는 2를 사용하면 API를 사용하는 사람들이 결과 할당을 간과하고 구현이 3 또는 4로 변경되면 코드가 손상 될 수 있다는 것입니다.

어쨌든, 당신이 볼 수 있듯이, 내 선호는 다른 개인적인 선호 (ID에 대한 세터 금지)에 기반하므로 모든 사람에게 적용되지 않을 수도 있습니다.


0

Java로 코딩 할 때 변경 가능한 유형의 각 객체 사용이 두 패턴 중 하나와 일치하도록해야합니다.

  1. 정확히 하나의 엔티티는 변경 가능한 오브젝트의 "소유자"로 간주되며 해당 오브젝트의 상태를 자체의 일부로 간주합니다. 다른 엔티티는 참조를 보유 할 수 있지만 참조 는 다른 사람이 소유 한 오브젝트를 식별 하는 것으로 간주해야 합니다.

  2. 객체가 변경 가능 함에도 불구하고 객체를 참조하는 사람은 객체를 변경할 수 없으므로 인스턴스를 효과적으로 변경할 수 없습니다 (Java 메모리 모델의 단점 때문에 효과적으로 불변 객체에 스레드를 만들 수있는 좋은 방법은 없습니다. 각 액세스에 추가적인 수준의 간접적 인 추가없이 안전한 불변의 의미론). 이러한 객체에 대한 참조를 사용하여 상태를 캡슐화하고 캡슐화 된 상태를 변경하려는 엔티티는 적절한 상태를 포함하는 새 객체를 작성해야합니다.

위의 두 번째 패턴을 맞추는 것처럼 보이기 때문에 메서드가 기본 개체를 변경하고 참조를 반환하는 것을 좋아하지 않습니다. 접근 방식이 도움이 될 수있는 몇 가지 경우가 있지만 객체를 변형시키는 코드 는 그렇게하는 것처럼 보일 것입니다. 같은 코드 는 단순히 myThing = myThing.xyz(q);객체 myThing보다 식별 된 객체를 변경하는 것처럼 보이지 않습니다 myThing.xyz(q);.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.