8 March 2009

'forget' keyword proposal

Instrukcje

Słowo 'forget' powodowało by wymazanie zmiennej z obecnego kontekstu. Co pozwoliło by na bardziej intuicyjne operowanie logiką programu, w przypadku gdy dana zmienna nie może(powinna być) być już używana. Ma to niebanalne znaczenie gdy po jakimś czasie wracamy do napisanego kodu.

Przegląd

Podsumowanie:
Rozszerzenie kontroli nad zasięgiem istnienia zmiennych.

Główne zalety / korzyści:

  • Brak konieczności wprowadzania sztucznych bloków. Czyli używania bloków do limitowania dostępu do zmiennych oraz wyodrębniania logicznie spójnych fragmentów kodu.
  • Czytelniejszy kod.
  • Mniej bugów (Możemy jasno określić gdy nie nie wolno dam już danej zmiennej użyć).

Najistotniejsze wady:

  • Trzeba zaimplementować.
  • Niektórzy mogą nadużywać tych samych nazw.

Inne rozwiązania:

  • Przeważnie można używać zmiennych o zasięgu bloku. Niestety zmuszeni jesteśmy wtedy wcześniej deklarować zmienne(przed blokiem/pascal like).
  • Podstawić null (najwyżej wyleci wyjątek).

Przykłady:

  public void read(String path) throws IOException {
    InputStreamReader isr = new InputStreamReader(new FileInputStream(new File(path)));
    int some = isr.read();
    // read
    isr.close();
    forget isr;
    // other operations
    InputStreamReader isr = new InputStreamReader(new FileInputStream(new File(path+".txt")));
    // other operations ...
  }
Detale rozwiązania:
Kompatybilność wsteczna:

Zapewniona pod warunkiem automatycznej zmiany nazw zmiennych, bądź przy możliwości współistnienia zmiennej o nazwie słowa kluczowego.

Kompatybilność w przód:

Zapewniona na poziome bytecodu

8 comments:

  1. Bez sensu, jeśli piszesz 1 metodę na 10 stron to na pewno nie robi ona jednej rzeczy, jeśli używasz w metodzie jednej zmiennej to raczej robisz to świadomie.

    Poza tym co to znaczy, że dana zmienna nie powinna być używana? Obiekt, czy referencja.

    Tak, czy siak, jak ustawisz referencje na null to i tak później nikt jej nie użyje, bo dostanie NPE.

    ReplyDelete
  2. Myśląc w ten sposób wychodzi, że walidacja typów w czasie kompilacji też jest bez sensu bo i tak się wyrzyga w czasie działania.

    Jak wracasz do kodu po np. 3 latach to możesz nie pamiętać, że zmienna nie powinna być używana, a tak piszesz:
    forget stream; // powód
    i nie dość, że masz jasno napisane dlaczego nie powinieneś (lub ktoś kto będzie zarządzał tym kodem) tego używać.

    Wybacz ale Java jest językiem w którym dąży się do tego, żeby wszystko było jawnie powiedziane bez używania kruczków, a ja jestem obydwiema rękami za tym.

    Pozdrawiam.

    ReplyDelete
  3. Ale ja dalej nie rozumiem sensu "zapominania" o zmiennej. Czy piszesz metody na 10 ekranów i używasz jednej zmiennej wielokrotnie jako referencje do różnych obiektów. Nawet jeśli to i tak po co zapominać o zmiennej wewnątrz metody... nie widzę konkretnego przykładu.

    ReplyDelete
  4. public static void readFile(final String path){
    File file = path.endsWith(".csv")?new File(path):new File(path.substring(0, path.length()-4)+".xml");
    forget path; // ścieżka powinna być pobierana ze zmiennej file
    }

    Mało polotu ma ten przykład ale mam nadzieje, że wystarczy.

    Pozdrawiam.

    ReplyDelete
  5. Ja też jakoś nie bałdzo rozumiem sensu tej instrukcji.

    1. Do 'zapomnienia' zmiennej w Javie/C++/(i w prawie każdym języku) masz instrukcje złożone:
    {
    Cośtam cośtam = ...;
    ...
    }
    // od tego momentu cośtam niedostępne
    To działa inaczej w ActionScripcie i JavaScripcie, ale jeden i drugi język to wielka kupa do bardziej zaawansowanej logiki.

    Jest to równoważne:
    Cośtam cośtam = ...;
    ...
    forget cośtam;

    2. Co do wymazywania argumentów funkcji (readFile) to rzeczywiście problem. Ale czy warto zmieniać język dla takiej pierdoły? Jak kod będzie rósł, prędzej czy później zrefaktoryzujesz go do takiej postaci (która rozwiąże i ten problem):

    public static void readFile(final String path){
    File file = path.endsWith(".csv")?new File(path):new File(path.substring(0, path.length()-4)+".xml");

    readFile(file);
    }

    private static void readFile(File path) {
    // napis path niewidoczny!!!
    ...
    }


    3. W przykładzie wyżej po prostu zastosowałem refaktoryzację ExtractMethod (zaznaczasz kod i Alt+Shift+M pod Eclipsem). Stosując analogiczny refaktoring ExtractClass można uniemożliwić pewnym metodom dostęp do takich a takich składowych klasy.
    To (między innymi) się nazywa enkapsulacją i do tego wcale nie trzeba zmieniać języka.
    (choć przyznam, bardzo brakuje mi w Javie czegoś pośredniego między dostępem public a domyślnym, tak by tylko klasy z danego jarka ale z innych pakieów mogły używać danej klasy)


    4. Używanie static czyni kod trudniejszym do zmian. Jak naprawdę chcesz mieć jeden obiekt użyj wzorca Singleton albo scope="singleton" w Springu.

    ReplyDelete
  6. Jest to pierdółka.
    Ale mnożenie metod na potęgę, bardzo utrudnia czytanie kodu.

    ReplyDelete
  7. Jeśli ta metoda jest króciutka chyba nic złego się nie stanie jak ten String będzie za długo widoczny. I tak będziesz miał pełną kontrolę nad tym co tam się dzieje. A forget powiedziałbym by tu niepotrzebnie skomplikował tą metodę.
    Jeśli ta metoda dłuższa dla czytelności czy reużywalności tak czy siak warto ją rozbić na mniejsze.

    Pakowanie wszystkiego w jedną metodę ma tylko sens gdy robisz megawypasiony algorytm do czegośtam, który ma przetworzyć powiedzmy kilka miliardów pikseli. Wtedy każde wywołanie metody z algorytmu rzeczywiście kosztuje tak bardzo, że w najbardziej zagnieżdżonej pętli algorytmu nie można używać wywołań metod, stringów czy kolekcji a jedynie operować na typach prostych i tablicach.
    W takiej (w Javie dosyć rzadkiej) sytuacji, gdy wszystko koniecznie musi siedzieć w jednej metodzie z powodów wydajnościowych, forget czasem ma jakiś sens. Ale jak byś to zaimplementował?:
    - jeśli się da, za pomocą { ... }; czasami po prostu da się przewidzieć w czasie kompilacji kiedy zmienna będzie nieużywana
    - jeśli nie da się tego przewidzieć w czasie kompilacji, zmienna jest final, jest referencją, i jest używana tylko do dobicia się do metod lub pól, kompilator może usunąć final i zamiast forget dać przypisanie: zmienna = null
    - tablic można używać podobnie jak wskaźników w C++, kompilator by wtedy opakowywał takie zmienne w tablice:
    T cośtam -> T[] cośtam = new T[1];
    cośtam = XXXX -> cośtam[0] = XXXX
    cośtam.metoda() -> cośtam[0].metoda()
    forget cośtam -> cośtam = null

    Tak czy siak, wszystkiego nie da się przewidzieć w czasie kompilacji, więc wygenerowany przez kompilator kod musiałby robić dodatkowe rzeczy by zrobić łubut jeśli zmienna jest zapomniana.
    Ale to się by kłóciło z jedynym potencjalnie słusznym zastosowaniem forget - do pisania megadużych metod w celu optymalizacji (czytaj wyżej). Napisałbyś megaszybki algorytm, a forget i tak by ci wszystko zamulił.

    Optymalizacja i ładny/krótki/rozwijalny/testowalny kod stoją po przeciwnej stronie barykady i jedno z drugim rzadko udaje się pogodzić.

    ReplyDelete
  8. Moim zdaniem rozbijanie ma sens tylko jeśli liczba przekazanych argumentów jest relatywnie mała do ilość linii kodu w nowej metodzie, bądź jest ona wywoływana kilka razy.
    W przeciwnym przypadku pogarsza to czytelność, a śledzenie przepływu informacji staje się męczące.

    Ta propozycja jest typowym rozszerzeniem idei WYSIWYG:
    What-You-See-Is-What-You-Get

    A jeśli nawet metoda jest krótka to czemu jawnie nie deklarować pewnych rzeczy, tylko zakładać, że wszystko będzie ok?

    ReplyDelete