[language-agnostic] 많은 정적 메서드를 사용하는 것이 나쁜가요?

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

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

그게 합리적인 접근인가?



답변

일반적인 정적 메서드에는 두 가지 종류가 있습니다.

  • “안전한”정적 메서드는 항상 동일한 입력에 대해 동일한 출력을 제공합니다. 전역을 수정하지 않으며 클래스의 “안전하지 않은”정적 메서드를 호출하지 않습니다. 기본적으로 제한된 종류의 함수형 프로그래밍을 사용하고 있습니다. 두려워하지 마세요. 괜찮습니다.
  • “안전하지 않은”정적 메서드는 전역 상태 또는 프록시를 전역 개체 또는 기타 테스트 할 수없는 동작으로 변경합니다. 이는 절차 적 프로그래밍에 대한 후퇴이며 가능하면 리팩토링해야합니다.

예를 들어 Singleton 패턴에서 “안전하지 않은”정적의 몇 가지 일반적인 용도가 있지만 이름이 예쁜 이름에도 불구하고 전역 변수를 변경하는 것입니다. 안전하지 않은 정적을 사용하기 전에 신중하게 생각하십시오.


답변

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

일반적으로 객체는 상태와 동작을 캡슐화합니다. 행동만을 캡슐화하는 객체는 이상합니다. 때로는 Lightweight 또는 Flyweight 의 예입니다. .

다른 경우에는 객체 언어로 수행되는 절차 설계입니다.


답변

이것은 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 / etc에서 정규 클래스 코드 생성) 클래스가 매우 길어지고 변환 메서드는 종종 복잡한 개체에 대한 진정한 고통이 될 수 있으며 원하는 경우 별도의 수업에서. 예, 유틸리티 클래스에 정적 메서드가 있습니다.


답변

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

즉, ‘리프’메서드 인 메서드 (상태를 수정하지 않고 단순히 입력을 어떻게 든 변형). 이에 대한 좋은 예는 Path.Combine과 같은 것입니다. 이러한 종류의 것들은 유용하고 간결한 구문을 만듭니다.

문제 내가 정적에있는 수많은 있습니다 :

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

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를 보면 생성자를 보면 어떤 초기화 단계를 수행해야하는지 알 수 없습니다. 클래스를 조사하여 종속성을 찾고 올바른 순서로 초기화해야합니다. 이 경우 실행하기 전에 ResourceLoader를 초기화해야합니다. 이제이 의존성 악몽을 확장하면 어떤 일이 일어날 지 짐작할 수 있습니다. 명시적인 초기화 순서가없는 코드를 유지하려고한다고 상상해보십시오. 인스턴스를 사용한 종속성 주입과 대조하십시오.이 경우 종속성이 충족 되지 않으면 코드가 컴파일 되지 않습니다!

또한 상태를 수정하는 정적을 사용하는 경우 카드의 집과 같습니다. 누가 무엇에 접근 할 수 있는지 결코 알 수 없으며 디자인은 스파게티 괴물과 비슷합니다.

마지막으로, 정적을 사용하는 것은 프로그램을 특정 구현에 연결하는 것입니다. 정적 코드는 테스트 가능성을위한 디자인의 반대입니다. 통계로 가득 찬 코드를 테스트하는 것은 악몽입니다. 정적 호출을 테스트 이중으로 바꿀 수는 없으므로 (정적 유형을 모방하도록 특별히 설계된 테스트 프레임 워크를 사용하지 않는 한) 정적 시스템은이를 사용하는 모든 것을 즉시 통합 테스트로 만듭니다.

요컨대, 정적은 어떤 것에는 괜찮으며 작은 도구 또는 일회용 코드에는 사용을 권장하지 않습니다. 그러나 그 이상으로 유지 보수성, 좋은 디자인 및 테스트 용이성에 대한 피비린내 나는 악몽입니다.

다음은 문제에 대한 좋은 기사입니다. 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에 소품.