Nazwa dla lekkich i ciężkich operacji

Odpowiedz Nowy wątek
2019-06-23 11:49
0

Otóż piszę funkcję która musi obsłużyć 10 przypadków

boolean check(Object o)

Dla 8 na 10 przypadków, operacja sprawdzająca jest bardzo szybka (kilkaset operacji na ms). Dla pozostałych 2óch na 10 przypadków szybka operacja sprawdzająca jest czasem nie wystarczająca, więc trzeba zrobić wolną operacją sprawdzającą. Kod powinien wyglądać tak:

boolean check(Object o) {
  if (szybka(o)) {
    return true;
  }
  return wolna(o);
}
  • Operacja szybka() potrafi tylko powiedzieć czy coś spełnia warunek:
    • true znaczy "na pewno spełnia"
    • false znaczy "nie wiadomo czy spełnia"
  • Operacja wolna() sprawdza już to na 100%:
    • true znaczy "na pewno spełnia"
    • false znaczy "na 100% nie spełnia"

No własnie, i nie wiem jak nazwać te metody, tak żeby było z samych nazw wiadomo:

  • Że szybka() jest optymalna, ale potrafi tylko powiedzieć "na pewno tak, ale nie wiadomo czy nie".
  • Że wolna() jest zasobożerna, ale potrafi na 100% ocenić poprawność.

Jakieś pomysły na nazwy?


edytowany 4x, ostatnio: TomRiddle, 2019-06-23 11:52
szybkaNiedokladnaOptymalna(); wolnaDokladnaNieoptymalna() xD - au7h 2019-06-23 11:55

Pozostało 580 znaków

2019-06-24 14:35
0
Spearhead napisał(a):

Takie rzeczy dopisz w dokumentacji funkcji.

Yyyyy.... albo mogę dobrze nazwać funkcję?

fasadin napisał(a):

shallow_check
deep_check

Nazwa shallowCheck nie mówi trochę o tym czy to jest false negative czy false positive.

yarel napisał(a):
  1. W interfejsie publicznym: check()
  2. W implementacji (prywatnej):
    • check_common_cases()
    • check_edge_cases()

Nazwy zupełnie nie mówią ani o tym która jest szybsza, ani o tym która jest pewna i nie pewna.

danek napisał(a):

A to są publiczne metody czy tylko wewnętrzna implementacja?

A co za różnica? Funkcja ma być nazwana dobrze.

yarel napisał(a):

Co jest ważne dla czytelnika? To CO funkcja robi, czy JAK to robi? Może po zmianach przez innego developera, już nie będzie taka "quick" / "slow" :-)

Na różnych poziomach abstrakcji funkcje będą miały różny poziom szczegółowości w nazwach i sygnaturze. Ten przypadek jest raczej nisko, więc siłą rzeczy nazwy funkcji muszą odzwierciedlać szczegóły implementacyjne.

Azarien napisał(a):

Pytanie czy ta szybka a niepewna w ogóle będzie przydatna, czy tylko tworzysz ją jako wadliwą optymalizację? :)

Dobre :D W 80% case'a ta "wadliwa optymalizacja" wystarczy żeby potwierdzić przypadki. Potwierdzanie jest tutaj dużo częstszym przypadkiem niż odrzucanie.

Michał Sikora napisał(a):

fastSensitiveCheck i slowSensitiveAndSpecificCheck - https://en.wikipedia.org/wiki/Sensitivity_and_specificity

Można dodać ewentualnie NotSpecific do pierwszego.

"Sensitive"/"Specific" nie mówią specjalnie dużo o tym czy funkcja dopuszcza false negative'y.


Pozostało 580 znaków

2019-06-24 14:38
cs
1

@TomRiddle Nie szkoda CI czasu na takie dywagacje, może po prostu:

boolean check(Object o) {
  return quickCheck(o) ? true : exactCheck(o);
}

Pozostało 580 znaków

2019-06-24 14:40
V-2
0
TomRiddle napisał(a):
V-2 napisał(a):

Jeśli znajdziesz zgrabniejsze rozwiązanie, wrzuć proszę do tematu (z ciekawości)

Raczej zostawię ify i znajdę dobre nazwy funkcji, zamiast zmieniać sygnatury :) Ale jeszcze raz dziękuję za wkład w odpowiedź.

Głównie dlatego że Optional.of(true)/Optional.empty() jest semantycznie równy true/false, więc nie specjalnie chciałbym go użyć.

Moim zdaniem nie jest ;) Po co wtedy byłby Boolean?

Logicznie rzecz biorąc, semantycznie równoważne true / false powinno być Optional.of(Void) i Optional.empty().

Niestety w Javie Optional.of(Void) jest chyba równy Optional.empty().

Ogólnie jest to jeden z tych przypadków, gdy brakuje union types.


Nie ma najmniejszego powodu, aby w CV pisać "email" przed swoim adresem mailowym, "imię i nazwisko" przed imieniem i nazwiskiem" ani "zdjęcie mojej głowy od przedniej strony" obok ewentualnego zdjęcia.

Pozostało 580 znaków

2019-06-24 14:49
1
TomRiddle napisał(a):

No przykro mi, ale nie tak mnie uczono programować. Jeśli mogę coś wyrazić dobrze w nazwie funkcji, to wolałbym to zrobić, niż olewać problem i pchać jakieś mało mówiące wyjaśnienie do zewnętrznego źródła, jakim jest dokumentacja. Polecam przeczytać "Clean Code".

No właśnie nie wyrażasz tego dobrze. Napisałeś całego posta z odpowiedziami na kilka prostych pytań, ale żadna z proponowanych nazw funkcji tego nie robi.

Szybko znaczy hashMap.containsKey('key').

A dlaczego druga metoda jest wolna? I mam teraz sprawdzać implementację obu metod, czy może lepiej będzie wcisnąć skrót w IDE i zobaczyć dokumentację z wyjaśnieniem?

Jestem przekonany że wyciąganie czegoś z hash mapy po kluczu jest wystarczająco szybkie :D

Czyli nie mierzyłeś, która metoda jest szybsza? A może różnica jest pomijalna? A może hashmapa powoduje duże zużycie pamięci i lepiej pozbyć się jej całkowicie? A może od czasu napisania tej metody ktoś zmienił hashmapę na inny słownik (lub na coś całkowicie innego) i już nie jest szybkie?

Jak patrzysz tu i teraz, to nie potrzebujesz nic, bo jesteś autorem kodu i wiesz, co on robi, ale jak za tydzień przyjdzie ktoś inny, to nagle Twoje „oczywistości” będą nieczytelne.

Nie mówiłem że jest "niepoprawny". Mówiłem że jest niepewny.
Jeśli klucz w mapie jest, znaczy że to co sprawdzam istnieje na 100%. Jeśli nie ma w mapie - to znaczy że może jest, może nie ma i trzeba wykonać ciężą operację żeby się upewnić. Około 80% przypadków jest już pre-loaded w mapie.

Dlaczego „około 80%”? Co znaczy „około”? Jak to zmierzono? A co, jeżeli od czasu napisania kodu to „około 80%” zmieniło się w „około 10%”? I jak powtórzyć testy?

Bo to szybki special-case, i warto zaoszczędzić trochę procesora/zasobów.

Ponownie, skąd wiadomo, że jest „szybki”? Skąd wiadomo, że oszczędzanie procesora jest tego warte, bo może tym samym rozpychamy pamięć i tracimy procesor na GC?

Fetchowanie tego drugi raz korzysta z zasobów IO systemowych (i czasami z socketów) - bez sensu jest wykonywanie tej drugiej operacji zawsze.

Skąd wiadomo, że w hashmapie będzie aktualna wartość? A co, jeżeli ta wartość się zmieniła w międzyczasie? Jak często odświeżana jest hashmapa? Czy jeżeli wartość nie jest aktualna, to kiedy staje się to problemem? Czy w ogóle? A dlaczego w ogóle hashmapa, dlaczego nie jakiś słownik ze słabymi referencjami?

Co do Clean Code, to nie trafiłeś, przeczytałem tę książkę.

edytowany 1x, ostatnio: Afish, 2019-06-24 14:50

Pozostało 580 znaków

2019-06-24 15:00
0

@TomRiddle: jeśli "fast check" to IMHO najlepsza nazwa to: check_in_cache.

Co do rozwiązania, to jeśli możesz je zakodować w postaci enuma tak jak powiedział @V-2 to nawet lepiej, bo wtedy możesz stworzyć listę metod jak np. w przyszłości pojawi się jakaś pośrednia metoda, która będzie dawała wynik wolniej niż cache, ale szybciej niż pełen przegląd to możesz dodać to bardzo łatwo do listy. Przykład:

#[derive(PartialOrd, Ord, PartialEq, Eq, Debug)]
enum CheckResult {
  Unknown,
  Maybe(bool),
  Sure(bool)
}

impl CheckResult {
  fn and_then<F: Fn() -> CheckResult, T>(&self, func: F) -> CheckResult {
    match *self {
      Sure(_) @ val => val,
      _ => func()
    }
  }
}

#[derive(Debug)]
struct Checker {
  cache: HashMap<u32, bool>
}

fn check_cache(cache: &HashMap<u32, bool>, value: u32) -> CheckResult {
  match cache.get(&value) {
    Some(result) => CheckResult::Sure(result),
    None => Unknown
  }
}

fn check_sure(cache: &HashMap<u32, bool>, value: u32) -> CheckResult {
  // …
}

fn check(cache: &HashMap<u32, bool>, value: u32) -> bool {
  let result =
    check_cache(cache, value)
    .and_then(|| check_sure(cache, value));

  match result {
    Sure(value) => value,
    other => panic!("Unsure value: {:?}", other)
  }
}
edytowany 1x, ostatnio: hauleth, 2019-06-25 11:22

Pozostało 580 znaków

2019-06-24 15:11
0
cs napisał(a):

@TomRiddle Nie szkoda CI czasu na takie dywagacje, może po prostu:

boolean check(Object o) {
  return quickCheck(o) ? true : exactCheck(o);
}

quickCheck() nie mówi o tym że jest tam false negative.

V-2 napisał(a):

Niestety w Javie Optional.of(Void) jest chyba równy Optional.empty().

A jak chcesz stworzyć instancję Void? :D

Afish napisał(a):

Szybko znaczy hashMap.containsKey('key').

A dlaczego druga metoda jest wolna?

Napisałem to w poście wyżej, bo korzysta z IO i z socketów.

I mam teraz sprawdzać implementację obu metod, czy może lepiej będzie wcisnąć skrót w IDE i zobaczyć dokumentację z wyjaśnieniem?

Idealnie by było gdyby nazwa była wymowna i nie musiałbyś szukać nigdzie indziej informacji o tym.

Jestem przekonany że wyciąganie czegoś z hash mapy po kluczu jest wystarczająco szybkie :D

Czyli nie mierzyłeś, która metoda jest szybsza?

Nie mierzyłem, ale mam mózg i wiem bez sprawdzania że implementacja oparta na mapie jest szybsza niż fetchowanie czegoś z IO/socket'ów.

A może różnica jest pomijalna?

Nie jest pomijalna.

A może hashmapa powoduje duże zużycie pamięci i lepiej pozbyć się jej całkowicie?

Nie mogę pozbyć się całkowicie hash'mapy bo ten szybki pre-test nie jest jej jedynym zadaniem, jest istotnym elementem innej części aplikacji (głównie UI).

A może od czasu napisania tej metody ktoś zmienił hashmapę na inny słownik (lub na coś całkowicie innego) i już nie jest szybkie?

Jeśli ktoś przebuduję część aplikacji, to faktycznie implementację fast/slow check'a trzeba będzie zmienić. Teraz to jest zupełnie nie potrzebne.

Jak patrzysz tu i teraz, to nie potrzebujesz nic, bo jesteś autorem kodu i wiesz, co on robi, ale jak za tydzień przyjdzie ktoś inny, to nagle Twoje „oczywistości” będą nieczytelne.

Właśnie dokładnie dlatego chcę poświęcić czas i dobrze nazwać tę funkcję - żeby dokładnie mówiła czym jest i co robi.

Nie mówiłem że jest "niepoprawny". Mówiłem że jest niepewny.
Jeśli klucz w mapie jest, znaczy że to co sprawdzam istnieje na 100%. Jeśli nie ma w mapie - to znaczy że może jest, może nie ma i trzeba wykonać ciężą operację żeby się upewnić. Około 80% przypadków jest już pre-loaded w mapie.

Dlaczego „około 80%”? Co znaczy „około”? Jak to zmierzono? A co, jeżeli od czasu napisania kodu to „około 80%” zmieniło się w „około 10%”? I jak powtórzyć testy?

A jakie to ma w ogóle znaczenie? Nawet gdyby to był 1%, to i tak warto zrobić pre-test, bo a nuż się okaże że pre-test dał true i wykonywanie ciężkiej operacji nie ma sensu.

Co znaczy „około”? Jak to zmierzono?

"około" znaczy 70-90%. Mniej więcej 8/10 case'ów - widzę po reakcji programu że trochę krócej mieli.

Bo to szybki special-case, i warto zaoszczędzić trochę procesora/zasobów.

Ponownie, skąd wiadomo, że jest „szybki”?

BO WYCIĄGANIE Z MAPY PO KLUCZU JEST JEDNĄ SZYBSZYCH OPERACJI W JAVIE. Szybsze chyba może być tylko dostęp do array'a po indexie.

Skąd wiadomo, że oszczędzanie procesora jest tego warte, bo może tym samym rozpychamy pamięć i tracimy procesor na GC?

No nie. Teraz to muszę pisać w assemblerze, skoro mapa jest taka nieoptymalna :/

Fetchowanie tego drugi raz korzysta z zasobów IO systemowych (i czasami z socketów) - bez sensu jest wykonywanie tej drugiej operacji zawsze.

Skąd wiadomo, że w hashmapie będzie aktualna wartość? A co, jeżeli ta wartość się zmieniła w międzyczasie? Jak często odświeżana jest hashmapa? Czy jeżeli wartość nie jest aktualna, to kiedy staje się to problemem? Czy w ogóle? A dlaczego w ogóle hashmapa, dlaczego nie jakiś słownik ze słabymi referencjami?

Chwila... próbujesz mi teraz powiedzieć że moja implementacja ma milion dziur? Czy twierdzisz że korzystanie z szybki pre-optymalizacji z możliwymi false-negative'ami jest niebezpieczne?

Jak to się ma do dobrego doboru nazwy?

Co do Clean Code, to nie trafiłeś, przeczytałem tę książkę.

No to na prawdę nie wiem czemu twierdzisz że nie warto poświęcać czasu na dobre nazwy.


Pozostało 580 znaków

2019-06-24 15:12
0
hauleth napisał(a):

@TomRiddle: jeśli "fast check" to IMHO najlepsza nazwa to: check_in_cache.

Co do rozwiązania, to jeśli możesz je zakodować w postaci enuma tak jak powiedział @V-2 to nawet lepiej, bo wtedy możesz stworzyć listę metod jak np. w przyszłości pojawi się jakaś pośrednia metoda, która będzie dawała wynik wolniej niż cache, ale szybciej niż pełen przegląd to możesz dodać to bardzo łatwo do listy. Przykład:

@hauleth Faktycznie Twoje odpowiedzi są bardzo merytoryczne, i wnoszą dużo do tematu. Ale:

Błaagaaam, bez over-enginerringu, mój kod wygląda tak

if (fastCheck(a)) {
  return true;
}
return slowCheck(a);

Nie widzę powodu żeby go refactorować - działa spoko. Chcę tylko zmienić nazwy funkcji na bardziej wymowne. Że jedna jest szybka ale nie pewna, a druga jest wolna ale pewna. Pomożecie?


edytowany 2x, ostatnio: TomRiddle, 2019-06-24 15:25

Pozostało 580 znaków

2019-06-24 16:33
0

Ciekawy problem. Może coś w stylu

if (preliminaryCheckMostCommonCases(a)) {
  return true;
}
return reliableExpensiveCheck(a);

?

Pozostało 580 znaków

2019-06-24 16:34
0
Potat0x napisał(a):

Ciekawy problem. Może coś w stylu

if (preliminaryCheckMostCommonCases(a)) {
  return true;
}
return reliableExpensiveCheck(a);

?

Nazwa preliminaryCheckMostCommonCases() nie mówi o tym że metoda jest szybka.


Pozostało 580 znaków

2019-06-24 16:37
0
TomRiddle napisał(a):

Napisałem to w poście wyżej, bo korzysta z IO i z socketów.

Tu nie chodzi o microbenchmark, tylko o działanie aplikacji. Ile razy wywołujesz tę funkcję i jak? Z każdym żądaniem HTTP? Co sekundę? Na wątku w tle? Na wątku UI? To, że unikniesz kilku cykli procesora w tym miejscu nie oznacza, że trzymanie hashmapy ma jakikolwiek sens.

Idealnie by było gdyby nazwa była wymowna i nie musiałbyś szukać nigdzie indziej informacji o tym.

Jeżeli piszesz cache, to napisz go poprawnie i nazwij metodę isYadaYadaCached. Jeżeli zaczniesz wymyślać coś innego, to tylko spowodujesz pytania.

Nie mierzyłem, ale mam mózg i wiem bez sprawdzania że implementacja oparta na mapie jest szybsza niż fetchowanie czegoś z IO/socket'ów.

Ponownie: GC? Czy to jest na wątku w tle? Czy na wątku UI? Czy to jest ścieżka krytyczna? Kto wypełnia słownik? Kto go czyści? Jak często?

Nie jest pomijalna.

O, to cenna informacja. To dlaczego nie jest pomijalna? Czy to jest ścieżka krytyczna? Co jeżeli ktoś za tydzień postanowi usunąć sprawdzanie w tej hashmapie, co się wywali?

Nie mogę pozbyć się całkowicie hash'mapy bo ten szybki pre-test nie jest jej jedynym zadaniem, jest istotnym elementem innej części aplikacji (głównie UI).

Czyli masz jakiś stan używany w innych miejscach aplikacji? Czy da się tego uniknąć? Czy da się te dwa słowniki rozdzielić? Ponownie: co jeżeli ktoś usunie sprawdzanie przy pomocy hashmapy, co się zepsuje?

Jeśli ktoś przebuduję część aplikacji, to faktycznie implementację fast/slow check'a trzeba będzie zmienić. Teraz to jest zupełnie nie potrzebne.

No i ponownie, skąd wiadomo, że trzeba będzie to zmienić? Jeżeli teraz masz jakieś argumenty na poparcie swojej implementacji, to dlaczego nie udokumentować ich?

Właśnie dokładnie dlatego chcę poświęcić czas i dobrze nazwać tę funkcję - żeby dokładnie mówiła czym jest i co robi.

Ponownie podtrzymuję swoją opinię, że nazwą nie przekażesz tego, co przekażesz dobrą dokumentacją.

A jakie to ma w ogóle znaczenie? Nawet gdyby to był 1%, to i tak warto zrobić pre-test, bo a nuż się okaże że pre-test dał true i wykonywanie ciężkiej operacji nie ma sensu.

Ma znaczenie, bo hashmapa zajmuje miejsce w pamięci, więc obciąża GC i serwer. Ponadto w słowniku mogą być niepoprawne dane i tym nieznaczącym kawałkiem kodu wprowadzisz buga.

"około" znaczy 70-90%. Mniej więcej 8/10 case'ów - widzę po reakcji programu że trochę krócej mieli.

Czy da się to zapisać jako test jednostkowy/wydajnościowy/jakikolwiek? Za tydzień ktoś będzie chciał to zmienić i nie będzie musiał myśleć, czy używanie dodatkowej ścieżki ma sens.

BO WYCIĄGANIE Z MAPY PO KLUCZU JEST JEDNĄ SZYBSZYCH OPERACJI W JAVIE. Szybsze chyba może być tylko dostęp do array'a po indexie.

Takie stwierdzenie jest bardzo ryzykowne, bo implementacji map jest mnóstwo, a nawet przy używaniu hashmap nie jest to oczywiste (chociażby kwestie wyliczania hasha dla stringa lub obiektu). No i znowu: co z GC?

No nie. Teraz to muszę pisać w assemblerze, skoro mapa jest taka nieoptymalna :/

Jeżeli usuniesz mapę, to nie będziesz musiał nic pisać.

Chwila... próbujesz mi teraz powiedzieć że moja implementacja ma milion dziur? Czy twierdzisz że korzystanie z szybki pre-optymalizacji z możliwymi false-negative'ami jest niebezpieczne?

Chcę się upewnić, że jeżeli w hashmapie będzie wartość, to będzie ona poprawna. Ponadto po Twoim opisie wyżej odnoszę wrażenie, że optymalizujesz tylko ścieżkę pozytywną (hashmapa zwraca true), a co z negatywną? Dlaczego wartości false nie trzymasz w hashmapie? Czy to efekt przeoczenia, czy ja źle zrozumiałem Twój kod, czy może wartość false w ogóle nie występuje?

No to na prawdę nie wiem czemu twierdzisz że nie warto poświęcać czasu na dobre nazwy.

Nigdzie tak nie powiedziałem.

Mój tok myślenia można byłoby uprościć do:

  • Jeżeli piszesz cache, to napisz go poprawnie (albo użyj gotowca z Guavy czy czegoś) i nazwij metodę blablaCached. Najlepiej też umieść w dokumentacji jakieś uzasadnienie, dlaczego postanowiłeś go użyć.
  • Jeżeli nie piszesz cache'a per se, to napisz w dokumentacji przesłanki do użycia tej szybkiej ścieżki. Nawet proste stwierdzenie w stylu fast check to avoid I/O operations as this seems to speed up things też jest wartościowe, inny programista zrozumie, że niespecjalnie cokolwiek mierzyłeś i kierowałeś się intuicją, więc jak trzeba będzie to zmieniać, to nie będzie godziny myślenia „a może to zepsuje rzeczy gdzieś indziej / a może muszę robić benchmarki, czy to się wyrobi na produkcji”. Możesz też nazwać funkcję checkYadaYadaFastWithoutIOWithFalseNegativesjeżeli tak bardzo chcesz.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0