„Statyczny” jako semantyczna wskazówka na temat bezpaństwowości?

11

Niedawno przeprowadziłem refaktoryzację średniego projektu w Javie, aby wrócić i dodać testy jednostkowe. Kiedy zdałem sobie sprawę, jak trudny jest kpić z singletonów i statyki, w końcu „zrozumiałem” o nich przez cały ten czas. (Jestem jedną z tych osób, które muszą uczyć się z doświadczenia. No cóż.)

Teraz, kiedy używam Sprężyny do tworzenia obiektów i łączenia ich, pozbywam się staticsłów kluczowych w lewo i w prawo. (Gdybym mógł potencjalnie chcieć się z niego kpić, to nie jest tak naprawdę statyczny w tym samym sensie, co Math.abs (), prawda?) Chodzi o to, że przyzwyczaiłem się używać staticdo oznaczania, że ​​metoda nie była oparta w dowolnym stanie obiektu. Na przykład:

//Before
import com.thirdparty.ThirdPartyLibrary.Thingy;
public class ThirdPartyLibraryWrapper {
    public static Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
ThirdPartyLibraryWrapper.newThingy(input);
//After
public class ThirdPartyFactory {
    public Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
thirdPartyFactoryInstance.newThingy(input);

Oto, gdzie robi się drażniąco. Podobał mi się stary sposób, ponieważ wielka litera powiedziała mi, że podobnie jak Math.sin (x), ThirdPartyLibraryWrapper.newThingy (x) za każdym razem robił to samo. Nie ma stanu obiektu, który mógłby zmienić sposób, w jaki obiekt robi to, o co go proszę. Oto kilka możliwych odpowiedzi, które rozważam.

  • Nikt inny tak się nie czuje, więc coś jest ze mną nie tak. Może po prostu tak naprawdę nie zinternalizowałem sposobu robienia OO! Może piszę w Javie, ale myślę w FORTRAN lub coś takiego. (Co byłoby imponujące, ponieważ nigdy nie napisałem FORTRAN.)
  • Może używam statyczności jako pewnego rodzaju proxy dla niezmienności dla celów rozumowania na temat kodu. Biorąc to pod uwagę, jakie wskazówki powinienem mieć w swoim kodzie, aby ktoś przybył, aby utrzymać go, aby wiedzieć, co jest stanowe, a co nie?
  • Być może powinno to być darmowe, jeśli wybiorę dobre metafory obiektów? np. thingyWrappernie wydaje się, że ma stan niezależny od owiniętego, Thingyktóry sam może być zmienny. Podobnie, thingyFactorydźwięki takie powinny być niezmienne, ale mogą mieć różne strategie wybrane spośród stworzenia.
leoger
źródło

Odpowiedzi:

12

Podobał mi się stary sposób, ponieważ wielka litera powiedziała mi, że podobnie jak Math.sin (x), ThirdPartyLibraryWrapper.newThingy (x) za każdym razem robił to samo. Nie ma stanu obiektu, który mógłby zmienić sposób, w jaki obiekt robi to, o co go proszę.

To jest właściwy sposób, aby to zrobić, tak. To powiedziawszy, staticnie daje żadnych gwarancji niezmienności lub trwałości państwa. To, że używasz go jako sugestii dla takich gwarancji, ma sens, ale w żadnym wypadku nie jest to zapewnione.

Rozważ następujące:

public static class Logger
{
    public static int LogLevel { get; set; }

    public static void Log(string message, int logLevel)
    {
        if (logLevel >= LogLevel)
        {
            // logs the message, but only if it is important enough.
        }
    }
}

Ta klasa nie tylko przechowuje stan, ale zachowanie Logger.Log()metody zmienia się wraz ze zmianą tego stanu. Jest to całkowicie uzasadnione ćwiczenie staticwzorców klasowych, ale nie ma żadnej z semantycznych gwarancji, które sugerujesz.

Robert Harvey
źródło
Dałem ci znacznik wyboru, ponieważ prezentujesz użyteczny kontrapunkt dla podstawy mojej intuicji, ale nadal chciałbym się zastanowić, jak powinienem reprezentować tę umowę / oczekiwanie na brak stanu i brak skutków ubocznych poprzez kodowanie konwencje.
leoger
1
Zwykle takie oczekiwania są zawarte w dokumentacji metody; to samo dotyczy bezpieczeństwa wątków.
Robert Harvey
5

Moim zdaniem to, co mówisz - wszystkie funkcje statyczne, które mają być nullipotentne - jest dobrym sposobem na pisanie kodu w większości przypadków, ale nie wierzę, że patrząc na funkcję statyczną, należy automatycznie założyć, że nie ma ona skutków ubocznych . Sytuacja może być bardziej złożona; weźmy na przykład funkcję, Class.forName(String)która wydaje się być bezstanowa, ale w rzeczywistości ładuje klasę do pamięci i ostatecznie tworzy instancję pól statycznych / uruchamia inicjalizator statyczny; jest to przykład funkcji idempotentnej (ewentualne wywołania po pierwszej nie mają znaczenia), ale nie jest to funkcja czysta (nie można powiedzieć, że nie ma skutków ubocznych). Jest to również dobry wybór, ale mogą zdarzyć się przypadki, w których trzy różne wywołania tej samej funkcji dadzą trzy różne wyniki; na przykład, jeśli zadzwoniszThread.currentThread() w aplikacji wielowątkowej nie ma gwarancji, że za każdym razem otrzymasz ten sam wątek.

Biorąc to pod uwagę, jakie wskazówki powinienem mieć w swoim kodzie, aby ktoś przybył, aby utrzymać go, aby wiedzieć, co jest stanowe, a co nie?

Właściwym rozwiązaniem jest dokumentowanie (na przykład Javadoc); także z nazwy funkcji i jej działania można czasem wywnioskować, że funkcja statyczna jest funkcją czystą. Na przykład nie ma powodu, aby ktoś wierzył, że Assert.assertTrue(boolean)z JUnit zmieniłby gdzieś jakiś stan. Z drugiej strony, gdy System.clearProperty(String)wywoływana jest taka funkcja , jest całkiem oczywiste, że wystąpią skutki uboczne.

m3th0dman
źródło
Jaka jest różnica między „nullipotent” a „bezpaństwowcem”?
Pacerier,
@Pacerier Nie powinno być żadnej różnicy.
m3th0dman
4

Biorąc to pod uwagę, jakie wskazówki powinienem mieć w swoim kodzie, aby ktoś przybył, aby utrzymać go, aby wiedzieć, co jest stanowe, a co nie?

Możesz ustawić je statycznie. FxCop w świecie C # popchnie cię do uczynienia rzeczy statycznymi, które nie powodują zmiennych elementów referencyjnych. Jest to właściwe postępowanie (zwykle).

Kiedy zdałem sobie sprawę, jak trudny jest kpić z singletonów i statyki, w końcu „zrozumiałem” o nich przez cały ten czas.

Przeniesienie ich wszystkich do instancji może być niewłaściwe. Zamiast tego oddzielić zależności od metod statycznych od klas, które ich używają. Możesz przetestować metody statyczne w izolacji, a następnie zastąpić zależność od konsumentów, kiedy będą musieli zostać przetestowani.

Telastyn
źródło
Czy możesz namalować przykład „oddzielenia zależności od metod statycznych”? Nie jest dla mnie jasne, co aktywujesz. Ukryłem już szczegóły implementacji w metodzie statycznej. Na koniec klasa, która z niej korzysta , musi osiągnąć tę funkcjonalność. Czy mówisz, że powinienem utworzyć obiekt instancji, który ma cienkie opakowania dla wszystkich metod statycznych?
leoger
1
@leoger - klasa korzystająca z metody statycznej nie musi osiągać funkcjonalności, jeśli testujesz tę klasę, w przeciwnym razie nie wyśmiewałbyś metody statycznej.
Telastyn