Czy istnieje powód, aby nie modyfikować wartości parametrów przekazywanych przez wartość?

9

Czy istnieją obiektywne, obsługiwane argumenty inżynierii oprogramowania przemawiające za lub przeciw modyfikowaniu wartości parametrów według wartości w treści funkcji?

Powtarzający się splot (głównie w dobrej zabawie) w moim zespole polega na tym, czy parametry przekazywane przez wartość powinny być modyfikowane. Kilku członków zespołu jest przekonanych, że nigdy nie należy przypisywać parametrów, tak aby wartość pierwotnie przekazana do funkcji zawsze mogła zostać zapytana. Nie zgadzam się i utrzymuję, że parametry są niczym więcej jak lokalnymi zmiennymi inicjowanymi przez składnię wywołania metody; jeśli oryginalna wartość parametru według wartości jest ważna, to można zadeklarować, aby zmienna lokalna wyraźnie zapisała tę wartość. Nie jestem pewien, czy którekolwiek z nas ma bardzo dobre poparcie dla naszej pozycji.

Czy jest to nierozwiązywalny konflikt religijny, czy też istnieją dobre, obiektywne powody inżynierii oprogramowania w obu kierunkach?

Uwaga: Kwestia zasady pozostaje bez względu na szczegóły implementacyjne danego języka. Na przykład w JavaScript, gdzie lista argumentów jest zawsze dynamiczna, parametry można traktować jako cukier składniowy do inicjalizacji zmiennych lokalnych z argumentsobiektu. Mimo to można traktować identyfikatory zadeklarowane jako „specjalne”, ponieważ nadal przechwytują przekazywanie informacji od osoby dzwoniącej do odbiorcy.

Joshua Honig
źródło
Nadal jest specyficzny dla języka; argumentowanie, że jest niezależne od języka, ponieważ „tak jest, z punktu widzenia mojego (ulubionego języka)” jest formą niepoprawnego argumentu. Drugim powodem, dla którego jest on specyficzny dla danego języka, jest to, że pytanie, na które nie ma uniwersalnej odpowiedzi dla wszystkich języków, podlega kulturom i normom każdego języka; w tym przypadku różne normy właściwe dla danego języka dają różne odpowiedzi na to pytanie.
rwong
Zasada, której członkowie twojego zespołu próbują cię nauczyć, to „jedna zmienna, jeden cel”. Niestety i tak nie znajdziesz ostatecznego dowodu. Co do tego, co jest warte, nie pamiętam, aby kiedykolwiek wybierać zmienną parametru do innego celu, ani też nie pamiętam, aby ją rozważać. Nigdy nie przyszło mi do głowy, że może to być dobry pomysł i nadal nie sądzę, żeby tak było.
Robert Harvey
Myślałem, że to musiało zostać zadane wcześniej, ale jedynym duplikatem, jaki udało mi się znaleźć, jest to starsze pytanie SO .
Doc Brown
3
Uznaje się, że język jest mniej podatny na błędy, aby zakazać mutacji argumentów, podobnie jak projektanci języków funkcjonalnych uważają, że przypisania „let” są mniej podatne na błędy. Wątpię, czy ktokolwiek udowodnił to założenie na podstawie badań.
Frank Hileman

Odpowiedzi:

15

Nie zgadzam się i utrzymuję, że parametry są niczym więcej jak lokalnymi zmiennymi zainicjowanymi przez składnię wywołania metody

Przyjmuję trzecią pozycję: parametry są jak zmienne lokalne: oba powinny być domyślnie traktowane jako niezmienne. Tak więc zmienne są przypisywane raz, a następnie tylko odczytywane, a nie zmieniane. W przypadku pętli for each (x in ...)zmienna jest niezmienna w kontekście każdej iteracji. Przyczyny tego są następujące:

  1. sprawia, że ​​kod łatwiej „wykonać w mojej głowie”,
  2. pozwala na bardziej opisowe nazwy zmiennych.

W obliczu metody z grupą zmiennych, które są przypisane, a następnie się nie zmieniają, mogę skupić się na czytaniu kodu, zamiast próbować zapamiętać bieżącą wartość każdej z tych zmiennych.

W obliczu tej samej metody, tym razem z mniejszymi zmiennymi, ale ciągle zmieniającymi wartość, muszę teraz pamiętać aktualny stan tych zmiennych, a także pracować nad tym, co robi kod.

Z mojego doświadczenia wynika, że ​​ten pierwszy jest znacznie łatwiejszy dla mojego biednego mózgu. Inni, mądrzejsi, ludowi ode mnie mogą nie mieć tego problemu, ale mi to pomaga.

Jeśli chodzi o drugi punkt:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

przeciw

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

Wymyślone przykłady, ale moim zdaniem jest o wiele bardziej jasne, co dzieje się w drugim przykładzie ze względu na nazwy zmiennych: przekazują one czytelnikowi znacznie więcej informacji niż ta masa rw pierwszym przykładzie.

David Arno
źródło
„zmienne lokalne… powinny być domyślnie traktowane jako niezmienne.” - Co?
whatsisname
1
forPętle z niezmiennymi zmiennymi są dość trudne . Hermetyzacja zmiennej w funkcji nie jest dla Ciebie wystarczająca? Jeśli masz problem ze śledzeniem zmiennych w zwykłej funkcji, być może twoje funkcje są za długie.
Robert Harvey
@RobertHarvey for (const auto thing : things)jest pętlą for, a zmienna, którą wprowadza, jest (lokalnie) niezmienna. for i, thing in enumerate(things):również nie
mutuje
3
Jest to ogólnie bardzo dobry model mentalny IMHO. Chciałbym jednak dodać jedną rzecz: często można zainicjować zmienną w więcej niż jednym kroku i traktować ją jako niezmienną później. Nie jest to sprzeczne z przedstawioną tutaj ogólną strategią, a jedynie z przestrzeganiem jej zawsze dosłownie.
Doc Brown
3
Wow, czytając te komentarze, widzę, że wiele osób tutaj oczywiście nigdy nie studiowało paradygmatu funkcjonalnego.
Joshua Jones
4

Czy jest to nierozwiązywalny konflikt religijny, czy też istnieją dobre, obiektywne powody inżynierii oprogramowania w obu kierunkach?

To jedna rzecz, którą bardzo lubię w (dobrze napisanym) języku C ++: możesz zobaczyć, czego się spodziewać. const string& x1jest stałym odniesieniem, string x2jest kopią i const string x3jest stałą kopią. Ja wiem , co się wydarzy w tej funkcji. x2 zostaną zmienione, ponieważ gdyby tak nie było, nie byłoby powodu, aby uczynić to non-const.

Więc jeśli masz język, który na to pozwala , zadeklaruj, co zamierzasz zrobić z parametrami. To powinien być najlepszy wybór dla wszystkich zaangażowanych.

Jeśli twój język na to nie pozwala, obawiam się, że nie ma srebrnego rozwiązania. Oba są możliwe. Jedyną wskazówką jest zasada najmniejszego zaskoczenia. Wybierz jedno podejście i używaj go w całej swojej bazie kodu, nie mieszaj go.

nvoigt
źródło
Inną dobrą rzeczą jest to, że int f(const T x)i int f(T x)różnią się tylko w funkcji-ciele.
Deduplicator
3
Funkcja C ++ z parametrem takim jak const int xpo prostu wyczyścić x nie jest zmieniana w środku? Wygląda mi na bardzo dziwny styl.
Doc Brown
@DocBrown Nie oceniam żadnego stylu, mówię tylko, że ktoś, kto uważa, że ​​parametrów nie należy zmieniać, może to zagwarantować kompilatorowi, a ktoś, kto uważa, że ​​ich zmiana jest w porządku, może to zrobić. Obie intencje można jasno przekazać za pomocą samego języka, co stanowi wielką zaletę w stosunku do języka, który nie może zagwarantować go w żaden sposób i w którym należy polegać na arbitralnych wytycznych i mieć nadzieję, że wszyscy je przeczytają i będą się z nimi zgadzać.
nvoigt
@nvoigt: jeśli ktoś woli zmodyfikować parametr funkcji („według wartości”), po prostu usunąłby constgo z listy parametrów, jeśli przeszkadza mu to. Więc nie sądzę, że to odpowiada na pytanie.
Doc Brown
@DocBrown To już nie jest const. Nie jest ważne, czy jest to stałe, czy nie, ważne jest, aby język zawierał sposób , aby bez wątpienia przekazać to programistom. Moja odpowiedź jest taka, że ​​nie ma znaczenia, o ile jasno to komunikujesz , co C ++ ułatwia, inni mogą tego nie robić i potrzebujesz konwencji.
nvoigt
1

Tak, jest to „konflikt religijny”, z wyjątkiem tego, że Bóg nie czyta programów (o ile mi wiadomo), więc został obniżony do konfliktu czytelności, który staje się kwestią osobistej oceny. I z pewnością to zrobiłem i widziałem, że zrobiono to na dwa sposoby. Na przykład,

void propagate (obiekt OBJECT *, podwójne t0, podwójne dt) {
  podwójne t = t0; / * lokalna kopia t0, aby uniknąć zmiany jej wartości * /
  podczas gdy (cokolwiek) {
    timestep_the_object (...);
    t + = dt; }
  powrót; }

Więc tutaj, tylko ze względu na nazwę argumentu t0 , prawdopodobnie nie zmienię jego wartości. Załóżmy jednak, że po prostu zmieniliśmy nazwę tego argumentu na tNow lub coś w tym rodzaju. Wtedy o wiele bardziej niż prawdopodobne zapomniałem o lokalnej kopii i po prostu napisałem tNow + = dt; dla tego ostatniego stwierdzenia.

Ale przeciwnie, załóżmy, że wiedziałem, że klient, dla którego program został napisany, ma zamiar zatrudnić nowych programistów z bardzo małym doświadczeniem, którzy czytaliby i pracowali nad tym kodem. Wtedy prawdopodobnie martwiłbym się pomyleniem ich z wartościami referencyjnymi i referencyjnymi, podczas gdy ich umysły są w 100% zajęte próbą przyswojenia całej logiki biznesowej. W takim przypadku zawsze deklaruję lokalną kopię każdego argumentu, który zostanie zmieniony, niezależnie od tego, czy jest dla mnie mniej lub bardziej czytelny.

Odpowiedzi na tego rodzaju pytania dotyczące stylu pojawiają się z doświadczeniem i rzadko mają kanoniczną odpowiedź religijną. Każdy, kto myśli, że jest jedna i tylko jedna odpowiedź (i że wie o tym :) prawdopodobnie potrzebuje więcej doświadczenia.

John Forkosh
źródło