문제

나는 클래스가 내부 상태를 추적할 필요가 없을 때 클래스의 모든 메서드를 정적으로 선언하는 경향이 있습니다.예를 들어 A를 B로 변환해야 하고 달라질 수 있는 일부 내부 상태 C에 의존하지 않는 경우 정적 변환을 생성합니다.조정할 수 있는 내부 상태 C가 있는 경우 생성자를 추가하여 C를 설정하고 정적 변환을 사용하지 않습니다.

정적 메서드를 과도하게 사용하지 말라는 다양한 권장 사항(StackOverflow 포함)을 읽었지만 여전히 위의 경험 법칙에서 무엇이 문제인지 이해하지 못합니다.

그것이 합리적인 접근인가, 아닌가?

도움이 되었습니까?

해결책

일반적인 정적 방법에는 두 가지 종류가 있습니다.

  • "안전한"정적 방법은 항상 동일한 입력에 대해 동일한 출력을 제공합니다. 그것은 글로벌을 수정하지 않으며 어떤 클래스의 "안전하지 않은"정적 메소드를 호출하지 않습니다. 본질적으로, 당신은 제한된 종류의 기능적 프로그래밍을 사용하고 있습니다. 이것을 두려워하지 말고 괜찮습니다.
  • "안전하지 않은"정적 방법은 글로벌 상태 또는 글로벌 객체 또는 기타 테스트 불가능한 행동으로의 프록시를 돌연변이합니다. 이것들은 절차 적 프로그래밍으로의 후퇴이며 가능한 경우 리팩토링되어야합니다.

예를 들어 싱글 톤 패턴에는 "안전하지 않은"통계에 대한 몇 가지 일반적인 용도가 있지만, 예쁜 이름에도 불구하고 당신은 그것들을 호출한다면, 당신은 글로벌 변수를 변형시키는 것입니다. 안전하지 않은 정적을 사용하기 전에 신중하게 생각하십시오.

다른 팁

내부 상태가없는 물체는 의심스러운 것입니다.

일반적으로 물체는 상태와 행동을 캡슐화합니다. 동작 만 캡슐화하는 대상은 홀수입니다. 때로는 그것은 예입니다 경량 또는 플라이급.

다른 경우에는 객체 언어로 수행 된 절차 디자인입니다.

이것은 실제로 John Millikin의 위대한 대답에 대한 후속 조치 일뿐입니다.


무국적 방법 (거의 기능)을 정적으로 만드는 것이 안전 할 수 있지만 때로는 수정하기 어려운 커플 링으로 이어질 수 있습니다. 다음과 같은 정적 방법이 있다고 생각합니다.

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

당신은 다음과 같이 부릅니다.

StaticClassVersionOne.doSomeFunkyThing(42);

정적 메소드의 동작을 수정 해야하는 케이스를 발견하고 단단히 묶여 있음을 알기 전까지는 모두 좋고 좋고 매우 편리합니다. StaticClassVersionOne. 아마도 코드를 수정할 수 있고 괜찮을 것입니다. 그러나 이전 행동에 의존하는 다른 발신자가 있다면 방법의 본문에서 설명해야합니다. 어떤 경우에는 이러한 모든 행동의 균형을 잡으려고 시도하면 그 방법 본문이 꽤 못 생겼거나 인재 할 수 없을 수 있습니다. 메소드를 분할하면 여러 장소에서 코드를 수정하여 새로운 클래스를 호출해야 할 수도 있습니다.

그러나 메소드를 제공하기 위해 인터페이스를 만들고 발신자에게 주어진 경우, 이제 동작이 변경되어야 할 때, 더 깨끗하고 쉽게 테스트하고 유지 관리하기 쉬운 인터페이스를 구현하기 위해 새로운 클래스를 만들 수 있습니다. 그리고 대신 발신자에게 주어집니다. 이 시나리오에서는 호출 클래스를 변경하거나 재 컴파일 할 필요가 없으며 변경 사항이 현지화됩니다.

상황이 가능하거나 상황이 아닐 수도 있지만 고려할 가치가 있다고 생각합니다.

다른 옵션은 원래 객체에서 비 정적 메소드로 추가하는 것입니다.

즉, 변경 :

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

~ 안으로

public class Bar {
    ...
    public Foo transform() { ...}
}

그러나 많은 상황에서 이것은 불가능하거나 (예 : XSD/WSDL/등의 일반 클래스 코드 생성) 또는 클래스를 매우 길게 만들 것이며 변환 방법은 종종 복잡한 물체에 대한 진정한 고통이 될 수 있으며 원합니다. 그들 자신의 별도의 수업에서. 예, 유틸리티 클래스에는 정적 방법이 있습니다.

정적 방법에서 멀어지게 된 이유는 그것들을 사용하면 물체의 장점 중 하나를 몰수하기 때문입니다. 객체는 데이터 캡슐화를위한 것입니다. 이것은 버그를 피하는 예기치 않은 부작용을 방지합니다. 정적 방법에는 캡슐화 된 데이터가 없으므로이 이점을 얻지 마십시오.

즉, 내부 데이터를 사용하지 않으면 사용하는 것이 좋고 실행하기에 약간 빠릅니다. 그래도 글로벌 데이터를 건드리지 않도록하십시오.

  • 일부 언어에는 또한 클래스 수준 변수가있어 데이터 및 정적 방법을 캡슐화 할 수 있습니다.

정적 클래스는 올바른 장소에서 사용되는 한 괜찮습니다.

즉, '잎'방법 인 방법 (상태를 수정하지 않고 입력을 어떻게 든 변환 함). 이것의 좋은 예는 path.combine과 같은 것들입니다. 이러한 종류의 것들이 유용하며 Terser 구문을 만듭니다.

그만큼 문제 나는 정적이 많다 :

첫째, 정적 클래스가있는 경우 종속성이 숨겨져 있습니다. 다음을 고려하세요:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

Texturemanager를 살펴보면 생성자를 보면서 어떤 초기화 단계를 수행 해야하는지 알 수 없습니다. 의존성을 찾아 올바른 순서로 초기화하려면 클래스를 탐구해야합니다. 이 경우 실행하기 전에 리소스 셀 로더를 초기화해야합니다. 이제이 의존성 악몽을 확장하면 어떤 일이 일어날 지 추측 할 수 있습니다. 명백한 초기화 순서가없는 곳에서 코드를 유지하려고한다고 상상해보십시오. 인스턴스와 의존성 주입과 대조하십시오.이 경우 코드조차 없습니다. 엮다 의존성이 충족되지 않은 경우!

또한 상태를 수정하는 정적을 사용하는 경우 카드의 집과 같습니다. 당신은 누가 무엇을 접근 할 수 있는지 알지 못하고 디자인은 스파게티 괴물과 비슷한 경향이 있습니다.

마지막으로, 스탯틱을 사용하는 것은 프로그램을 특정 구현과 연결합니다. 정적 코드는 테스트 가능성을위한 설계의 대립입니다. 정적으로 수수께끼의 테스트 코드는 악몽입니다. 정적 호출은 테스트 더블로 교체 할 수 없습니다 (정적 유형을 조정하도록 특별히 설계된 테스트 프레임 워크를 사용하지 않는 한) 정적 시스템은 즉각적인 통합 테스트로 사용하는 모든 것을 유발합니다.

요컨대, 정적은 어떤 것들과 작은 도구 나 낙담 코드에 대해서는 괜찮습니다. 나는 그들의 사용을 낙담시키지 않을 것입니다. 그러나 그 외에도 그들은 유지 가능성, 좋은 디자인 및 테스트의 용이성을위한 피의 악몽입니다.

다음은 문제에 대한 좋은 기사입니다. http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

그것은 합리적인 접근법 인 것 같습니다. 너무 많은 정적 클래스/메소드를 사용하고 싶지 않은 이유는 객체 지향 프로그래밍에서 멀어지고 구조화 된 프로그래밍 영역으로 넘어 가기 때문입니다.

당신이 단순히 a를 b로 변환하는 경우, 우리가하는 모든 일은 텍스트를 바꾸는 것입니다.

"hello" =>(transform)=> "<b>Hello!</b>"

그러면 정적 방법이 의미가 있습니다.

그러나 객체에서 이러한 정적 메소드를 자주 호출하고 많은 통화에 대해 고유 한 경향이있는 경우 (예 : 사용 방식은 입력에 따라 다릅니다) 객체의 고유 동작의 일부인 경우 그것을 대상의 일부로 만들고 상태를 유지하는 것이 현명해야합니다. 이를 수행하는 한 가지 방법은 인터페이스로 구현하는 것입니다.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

편집 : 정적 방법을 잘 사용하는 좋은 예는 ASP.NET MVC 또는 Ruby의 HTML 헬퍼 방법입니다. 그들은 물체의 동작과 관련이없는 HTML 요소를 만듭니다. 따라서 정적입니다.

편집 2 : 기능적 프로그래밍을 구조화 된 프로그래밍으로 변경했습니다 (어떤 이유로 혼란 스러웠습니다),이를 지적하기 위해 Torsten에게 소품.

최근에 처음에 정적 클래스로 구현 된 일부 클래스를 제거/수정하는 응용 프로그램을 리팩토링했습니다. 시간이 지남에 따라이 수업은 너무 많이 인수되었고 사람들은 새로운 기능을 정적으로 태그로 계속 태그를 남겼습니다.

따라서 내 대답은 정적 클래스가 본질적으로 나쁘지는 않지만 지금 인스턴스를 만들기가 더 쉬울 수 있다는 것입니다. 그런 다음 나중에 리팩터를 리팩터링해야합니다.

나는 정적 방법과 싱글 톤을 가진 수업 사이를왔다 갔다했다. 둘 다 문제를 해결하지만 싱글 톤은 훨씬 더 쉽게 둘 이상으로 대체 될 수 있습니다. (프로그래머는 항상 무언가 중 하나만있을 수있을 정도로 확신하는 것처럼 보이며 매우 제한된 경우를 제외하고 정적 방법을 완전히 포기하기에 충분한 시간이 잘못되었음을 알았습니다).

어쨌든, 싱글 톤은 나중에 다른 인스턴스를 얻기 위해 공장에 무언가를 전달하고 리팩토링없이 전체 프로그램의 동작을 변경할 수 있습니다. 글로벌 클래스의 정적 메소드를 다른 "백업"데이터 또는 약간 다른 행동 (아동 클래스)으로 변경하는 것은 엉덩이의 주요 고통입니다.

정적 방법은 유사한 이점이 없습니다.

그렇습니다. 그들은 나쁘다.

나는 그것을 디자인 냄새라고 생각합니다. 주로 정적 방법을 사용하고 있다면 아마도 좋은 oo 디자인이 없을 것입니다. 반드시 나쁘지는 않지만 모든 냄새와 마찬가지로 멈추고 재평가하게됩니다. 그것은 당신이 더 나은 oo 디자인을 만들 수 있거나 다른 방향으로 가서이 문제에 대해 완전히 피해야한다는 것을 암시합니다.

내부 상태가 작동하지 않는 한 괜찮습니다. 일반적으로 정적 메소드는 스레드 안전성이 될 것으로 예상되므로 도우미 데이터 구조를 사용하는 경우 스레드 안전 방식으로 사용하십시오.

당신이 알게된다면 절대 C의 내부 상태를 사용해야합니다. 괜찮습니다. 그러나 미래에 변화가 없다면, 방법을 정적이 아닌 방법으로 만들어야합니다. 처음부터 정적이 아닌 경우, 필요하지 않은 경우 내부 상태를 무시할 수 있습니다.

유틸리티 방법이라면 정적으로 만드는 것이 좋습니다. Guava와 Apache Commons는이 원칙에 따라 구축되었습니다.

이것에 대한 나의 의견은 순전히 실용적입니다. 앱 코드 인 경우 정적 메소드가 일반적으로 최선의 방법이 아닙니다. 정적 방법에는 심각한 단위 테스트 제한이있어 쉽게 조롱 할 수 없습니다. 다른 테스트에 조롱 된 정적 기능을 주입 할 수 없습니다. 또한 일반적으로 기능을 정적 메소드에 주입 할 수 없습니다.

따라서 내 앱 로직에는 일반적으로 작은 정적 유틸리티와 같은 메소드 호출이 있습니다. 즉

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

이점 중 하나는 내가 그러한 방법을 테스트하지 않는다는 것입니다 :-)

물론은 총알은 없습니다. 정적 클래스는 작은 유틸리티/도우미에게는 괜찮습니다. 그러나 비즈니스 로직 프로그래밍에 정적 방법을 사용하는 것은 확실히 악합니다. 다음 코드를 고려하십시오

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

정적 메소드 호출이 나타납니다 ItemsProcessor.SplitItem(newItem); 원인이 냄새가납니다

  • 당신은 명시적인 종속성이 표시되지 않으며 코드를 파헤 치지 않으면 클래스와 정적 메소드 컨테이너의 커플 링이 간과 될 수 있습니다.
  • 당신은 테스트 할 수 없습니다 BusinessService 그것을 분리합니다 ItemsProcessor (대부분의 테스트 도구는 정적 클래스를 조롱하지 않으며 단위 테스트가 불가능합니다. 단위 테스트 없음 == 낮은 품질

정적 메서드는 일반적으로 상태 비저장 코드에도 나쁜 선택입니다.대신 한 번 인스턴스화되고 해당 메서드를 사용하려는 클래스에 삽입되는 이러한 메서드를 사용하여 싱글톤 클래스를 만듭니다.이러한 클래스는 모의하고 테스트하기가 더 쉽습니다.그들은 훨씬 더 객체 지향적입니다.필요할 때 프록시로 래핑할 수 있습니다.정적은 OO를 훨씬 더 어렵게 만들고 거의 모든 경우에 이를 사용할 이유가 없다고 생각합니다.100%는 아니지만 거의 다 그렇습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top