Czy definiowanie zmiennej w celu nazwania argumentu metody jest dobrą praktyką?


73

Ze względu na czytelność często zdarza mi się definiować zmienne tymczasowe podczas wywoływania funkcji, takich jak poniższy kod

var preventUndo = true;
doSomething(preventUndo);

Krótszą wersją tego byłoby:

doSomething(true);

Ale kiedy wracam do kodu, często zastanawiam się, o co chodzi true. Czy istnieje konwencja dla tego rodzaju zagadki?


5
Czy używasz jakiegoś IDE? Większość będzie miała jakiś sposób wskazania parametrów wywoływanej funkcji, co w pewnym stopniu niweluje potrzebę wykonania tej czynności.
Matthew Scharley,

4
Nie używam IDE, ale nawet wtedy przypuszczam, że podpowiedź wymagałaby jakiegoś działania (najechanie kursorem lub przejście do linii). Lubię wiedzieć, co robi mój kod, patrząc na niego.
methodofaction

11
Jeśli język to obsługuje, możesz użyć wyliczenia, takiego jakdoSomething( Undo.PREVENT )
James P.

4
Ale możesz zdefiniować Undo = { PREVENT = true, DONT_PREVENT = false }. Ale w JavaScript konwencja polega na robieniu tego w następujący sposób: function myFunction( mandatoryArg1, mandatoryArg2, otherArgs ) { /*...*/ }i wtedy myFunction( 1, 2, { option1: true, option2: false } ).
xavierm02

6
To zdecydowanie zależy od języka. Gdyby to był na przykład python, sugerowałbym użycie argumentów słów kluczowych, np.doSomething(preventUndo=True)
Joel Cornett

Odpowiedzi:


117

Wyjaśnianie zmiennych

Twój przypadek jest przykładem wprowadzenia wyjaśniającego refaktoryzację zmiennych . Krótko mówiąc, zmienna objaśniająca to zmienna, która nie jest absolutnie niezbędna, ale pozwala nadać czemuś wyraźną nazwę w celu zwiększenia czytelności.

Kod dobrej jakości komunikuje intencje czytelnikowi; a jako profesjonalny programista czytelność i łatwość konserwacji są Twoimi najważniejszymi celami.

Dlatego zalecam następującą zasadę: jeśli cel twojego parametru nie jest od razu oczywisty, możesz użyć zmiennej, aby nadać jej dobrą nazwę. Myślę, że jest to ogólnie dobra praktyka (chyba że jest nadużywana). Oto szybki, wymyślony przykład - zastanów się:

editButton.Enabled = (_grid.SelectedRow != null && ((Person)_grid.SelectedRow).Status == PersonStatus.Active);

w porównaniu z nieco dłuższym, ale prawdopodobnie wyraźniejszym:

bool personIsSelected = (_grid.SelectedRow != null);
bool selectedPersonIsEditable = (personIsSelected && ((Person)_grid.SelectedRow).Status == PersonStatus.Active)
editButton.Enabled = (personIsSelected && selectedPersonIsEditable);

Parametry boolowskie

Twój przykład pokazuje, dlaczego booleany w interfejsach API są często złym pomysłem - po stronie wywołującej nic nie robią, aby wyjaśnić, co się dzieje. Rozważać:

ParseFolder(true, false);

Musisz sprawdzić, co oznaczają te parametry; gdyby były wyliczeniami, byłoby o wiele bardziej jasne:

ParseFolder(ParseBehaviour.Recursive, CompatibilityOption.Strict);

Edytować:

Dodano nagłówki i zamieniłem kolejność dwóch głównych akapitów, ponieważ zbyt wiele osób skupiało się na części z parametrami boolowskimi (szczerze mówiąc, był to pierwszy akapit). Dodano także przykład do pierwszej części.


28
Chyba że użyjesz nazwanych parametrów (nazwanych argumentów w .NET Framework), w takim przypadku doSomething(preventUndo: true);jest to wystarczająco jasne. Czy tworzysz wyliczenie dla każdego logicznego kodu w twoim API? Poważnie?
Arseni Mourzenko

12
@MainMa: Jeśli używany język nie jest wystarczająco fajny, aby obsługiwać nazwane argumenty, dobrym rozwiązaniem jest użycie wyliczenia. Nie oznacza to, że powinieneś systematycznie wprowadzać wyliczenia wszędzie.
Giorgio

18
@MainMa - Aby odpowiedzieć na to pytanie, chodzi o to, że nie powinieneś mieć boolanów w swoich interfejsach API (przynajmniej tych publicznych). Musisz zmienić składnię nazwanych argumentów, która prawdopodobnie nie jest idiomatyczna (w momencie pisania), a w każdym razie parametr boolowski prawie zawsze oznacza, że ​​twoja metoda robi dwie rzeczy i pozwalasz wywołującemu na wybierać. Ale moja odpowiedź brzmiała: nie ma nic złego w używaniu wyjaśniania zmiennych w kodzie; to właściwie bardzo dobra praktyka.
Daniel B

3
Jest to stała część języka C #, czego więcej „akceptacji głównego nurtu” potrzebuje? Nawet dla kogoś, kto nie jest jeszcze świadomy konstrukcji (co oznacza, że ​​twoi twórcy C # nawet nie czytali dokumentów C #), jest dość oczywiste, co robi.
Nate CK

3
Właśnie dlatego MS publikuje te miłe podsumowania wszystkich nowych rzeczy, które dodali w każdej wersji, nie sądzę, że przeczytanie jednego z nich co kilka lat wymaga
Nate CK

43

Nie pisz kodu, którego nie potrzebujesz.

Jeśli doSomething(true)trudno ci zrozumieć, dodaj komentarz:

// Do something and prevent the undo.
doSomething(true);

lub, jeśli język to obsługuje, dodaj nazwę parametru:

doSomething(preventUndo: true);

W przeciwnym razie polegaj na swoim IDE, aby uzyskać podpis wywoływanej metody:

wprowadź opis zdjęcia tutaj

Przypadki, w których jest to przydatne

Przydanie dodatkowej zmiennej może być przydatne:

  1. Do celów debugowania:

    var productId = this.Data.GetLastProductId();
    this.Data.AddToCart(productId);
    

    Ten sam kod można zapisać w jednym wierszu, ale jeśli chcesz dodać punkt przerwania przed dodaniem produktu do koszyka, aby sprawdzić, czy identyfikator produktu jest poprawny, dobrym pomysłem jest napisanie dwóch wierszy zamiast jednego.

  2. Jeśli masz metodę z dużą ilością parametrów, a każdy parametr przechodzi od oceny. W jednym wierszu może to stać się całkowicie nieczytelne.

    // Even with indentation, this is unreadable.
    var doSomething(
        isSomethingElse ? 0 : this.getAValue(),
        this.getAnotherOne() ?? this.default,
        (a + b + c + d + e) * f,
        this.hello ? this.world : (this.hello2 ? this.world2 : -1));
    
  3. Jeśli ocena parametru jest zbyt skomplikowana. Przykład kodu, który widziałem:

    // Wouldn't it be easier to have several if/else's (maybe even in a separate method)?
    do(something ? (hello ? world : -1) : (programmers ? stackexchange : (com ? -1 : 0)));
    

Dlaczego nie w innych przypadkach?

Dlaczego nie powinieneś tworzyć dodatkowych zmiennych w prostych przypadkach?

  1. Nie z powodu wpływu na wydajność. Byłoby to bardzo błędne założenie początkującego programisty, który mikrooptymalizuje swoją aplikację. W większości języków nie ma wpływu na wydajność , ponieważ kompilator wstawi zmienną. W tych językach, w których kompilator tego nie robi, możesz uzyskać kilka mikrosekund, wkładając go ręcznie, co nie jest tego warte. Nie rób tego

  2. Ale ze względu na ryzyko zdysocjowania nazwy, którą nadajesz zmiennej do nazwy parametru.

    Przykład:

    Powiedzmy, że oryginalny kod to:

    void doSomething(bool preventUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code:
    var preventUndo = true;
    doSomething(preventUndo);
    

    Później programista pracujący nad doSomethingpowiadomieniami, że ostatnio historia cofania wprowadziła dwie nowe metody:

    undoHistory.clearAll() { ... }
    undoHistory.disable() { ... }
    

    Teraz preventUndowydaje się niezbyt jasne. Czy to oznacza, że ​​zapobiegnie się tylko ostatniej czynności? A może oznacza to, że użytkownik nie będzie już mógł korzystać z funkcji cofania? A może historia cofania zostanie wyczyszczona? Jaśniejsze nazwy to:

    doSomething(bool hideLastUndo) { ... }
    doSomething(bool removeAllUndo) { ... }
    doSomething(bool disableUndoFeature) { ... }
    

    Więc teraz masz:

    void doSomething(bool hideLastUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code, very error prone, while the signature of the method is clear:
    var preventUndo = true;
    doSomething(preventUndo);
    

Co z wyliczeniami?

Niektóre inne osoby sugerowały użycie wyliczeń. Chociaż rozwiązuje natychmiastowy problem, tworzy większy. Spójrzmy:

enum undoPrevention
{
    keepInHistory,
    prevent,
}

void doSomething(undoPrevention preventUndo)
{
    doTheJob();
    if (preventUndo == undoPrevention.prevent)
    {
        this.undoHistory.discardLastEntry();
    }
}

doSomething(undoPrevention.prevent);

Problemy z tym:

  1. To za dużo kodu. if (preventUndo == undoPrevention.prevent)? Poważnie?! Nie chcę pisać za ifkażdym razem.

  2. Dodanie elementu do wyliczenia jest później bardzo kuszące, jeśli używam tego samego wyliczenia gdzie indziej. Co jeśli zmodyfikuję to w ten sposób:

    enum undoPrevention
    {
        keepInHistory,
        prevent,
        keepButDisable, // Keeps the entry in the history, but makes it disabled.
    }
    

    Co się teraz stanie? Czy doSomethingmetoda będzie działać zgodnie z oczekiwaniami? Aby temu zapobiec, od początku wymagałoby zapisania tej metody:

    void doSomething(undoPrevention preventUndo)
    {
        if (![undoPrevention.keepInHistory, undoPrevention.prevent].contains(preventUndo))
        {
            throw new ArgumentException('preventUndo');
        }
    
        doTheJob();
        if (preventUndo == undoPrevention.prevent)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

    Wariant wykorzystujący booleany zaczyna wyglądać tak ładnie!

    void doSomething(bool preventUndo)
    {
        doTheJob();
        if (preventUndo)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

3
„To za dużo kodu. If (preventUndo == undoPrevention.prevent)? Poważnie ?! Nie chcę pisać takich ifs za każdym razem.”: Co powiesz na użycie starej dobrej instrukcji switch? Ponadto, jeśli używasz wartości logicznej, i tak potrzebujesz instrukcji if. Szczerze mówiąc, wasza krytyka w tej kwestii wydaje mi się trochę sztuczna.
Giorgio

1
Brakuje mi tutaj alternatywnego rozwiązania. Czy mówisz, że powinien trzymać się powiedzenia „nic” prawda, fałsz i polegać na IDE (którego nie używa)? Komentarz może zgnić jak nazwa zmiennej.
Zabezpiecz

2
@ superM: Nie jest mi trudniej zmienić nazwę tymczasowej zmiennej używanej tylko do wywołania. Jeśli nazwa wyliczenia zostanie zmieniona, będzie jeszcze lepiej, ponieważ kod połączenia nie będzie już działał i wyśle ​​błąd prosto w twarz. Jeśli funkcjonalność funkcji zostanie radykalnie zmieniona przez rozszerzenie enum, musisz ponownie sprawdzić wszystkie wezwania do dalszej poprawności, w przeciwnym razie programujesz z nadzieją, a nie z logiką. Nawet nie myśleć o zmianie wartości, np negując preventUndoaby allowUndow podpisie ...
Bezpieczne

4
@superM utrzymywanie komentarzy w synchronizacji z kodem jest bardzo podatne na problemy, ponieważ kompilator nie ma pomocy w poszukiwaniu niespójności.
Buhb

7
„... polegaj na swoim IDE, aby uzyskać podpis wywoływanej metody” - jest to przydatne, gdy piszesz kod, ale nie tak bardzo, gdy go czytasz. Kiedy czytam kod dla klasy, chcę wiedzieć, co ona robi, po prostu patrząc na to. Jeśli musisz polegać na IDE dla czytelności, twój kod nie jest samodokumentujący.
Phil

20

Nie powinieneś także pisać metod z flagami logicznymi.

Cytując Roberta C. Martina w swojej książce Clean Code (ISBN-13 978-0-13-235088-4): „Przekazywanie funkcji logicznej do funkcji jest naprawdę okropną praktyką”.

Parafrazując go, rozumowanie jest takie, że fakt, że masz przełącznik prawda / fałsz, oznacza, że ​​twoja metoda najprawdopodobniej robi dwie różne rzeczy (tj. „Zrób coś z cofnięciem” i „Zrób coś bez cofnięcia”) i dlatego powinna być podzielone na dwie różne metody (które mogą wewnętrznie wywoływać to samo).

DoSomething()
{...

DoSomethingUndoable()
{....

7
Zgodnie z tą logiką mój pojedynczy konstruktor HotDog musiałby mieć około 16 różnych metod: HotDogWithMustardRelishKrautOnion (), HotDogWithMustardNorelishKrautOnion () i tak dalej. Lub może to być prosty HotDogWithOptions (opcje int), w którym interpretuję opcje jako pole bitowe, ale wracamy do jednej metody, która robi kilka rzekomo różnych rzeczy. Więc jeśli wybiorę pierwszą opcję, czy muszę napisać duży warunek, aby ustalić, który konstruktor ma zostać wywołany na podstawie rzeczy, które w innym przypadku byłyby parametrami boolowskimi? A jeśli Q używa int, to A nie odpowiada Q.
Caleb

4
Po przeczytaniu wszystkich odpowiedzi i przejrzeniu mojego kodu dochodzę do wniosku, że jest to właściwy sposób na zrobienie tego. doSomethingUndoable()mógł uchwycić zmiany, a następnie zadzwonićdoSomething()
methodofaction

1
Więc jeśli mam funkcję, która dużo przetwarza i ma opcję wysłania wiadomości e-mail po jej zakończeniu, nie powinienem mieć logicznego argumentu, który mógłby uniemożliwić wysłanie wiadomości e-mail, czy powinienem utworzyć osobną funkcję?
yakatz

2
@Caleb W tym przypadku albo zrobię hot-doga i dodam składniki jeden po drugim, lub w silnie napisanym języku przekażę obiekt HotDogSettings, w którym ustawiłbym wszystkie flagi boolowskie. Nie miałbym 15 różnych flag prawda / fałsz, które byłyby koszmarem do utrzymania. pamiętaj, że kod dotyczy konserwacji, a nie pisania, bo to 80% kosztów aplikacji. var hd = MakeHotDog (); hd.Add (Cebula); ...
Nick B.

2
@Caleb Myślę, że różnica polega na tym, że twój przykład używa boolanów jako rzeczywistego pola danych, podczas gdy większość jego zastosowań służy do napędzania przepływu wykonania; stąd poręcz wujka Boba przeciw niemu. Ponadto jego rada jest nieco skrajna - kilka następnych wierszy w książce odradza posiadanie więcej niż 2 parametrów, co może być jeszcze bardziej ekstremalną postawą. Jest jednak metoda na szaleństwo. Masz absolutną rację, że to nie odpowiada na pytanie, błąd, który popełniłem we własnej odpowiedzi.
Daniel B

5

Gdy spojrzysz na dwie klasy, aby zobaczyć, jak są ze sobą powiązane, jedną z kategorii jest łączenie danych , które odnosi się do kodu w jednej klasie wywołującego metody innej klasy i po prostu przekazującego dane, na przykład za jaki miesiąc chcesz raport, i inną kategoria to sprzężenie sterujące , wywoływanie metod i przekazywanie czegoś, co kontroluje zachowanie metody. Przykład, którego używam na zajęciach, to verboseflaga lub reportTypeflaga, ale preventUndotakże świetny przykład. Jak właśnie wykazałeś, sprzężenie sterujące utrudnia odczyt kodu wywoławczego i zrozumienie, co się dzieje. Naraża również kod wywołujący na zmiany, doSomething()które używają tej samej flagi do kontrolowania zarówno cofania, jak i archiwizowania lub dodawania innego parametrudoSomething() kontrolować archiwizację, w ten sposób psując kod i tak dalej.

Problem polega na tym, że kod jest zbyt ściśle powiązany. Przekazywanie wartości logicznej w celu kontrolowania zachowania jest, moim zdaniem, oznaką złego interfejsu API. Jeśli posiadasz interfejs API, zmień go. Dwie metody doSomething()i doSomethingWithUndo()byłoby lepiej. jeśli nie jesteś właścicielem, napisz dwie metody otoki w kodzie, który posiadasz, i jedną z nich wywołaj, doSomething(true)a drugą wywołaj doSomething(false).


4

Określenie roli argumentów nie jest zadaniem osoby wywołującej. Zawsze wybieram wersję wbudowaną i sprawdzam podpis wywoływanej funkcji, jeśli mam wątpliwości.

Zmienna powinna być nazwana na podstawie ich roli w bieżącym zakresie. Nie Zakres, w którym są wysyłane.


1
rolą zmiennej w bieżącym zakresie jest określenie, do czego jest używana. Nie rozumiem, w jaki sposób zaprzecza to stosowaniu zmiennych tymczasowych przez OP.
superM

4

W JavaScripcie chciałbym, aby funkcja przyjmowała obiekt jako jedyny parametr mniej więcej taki:

function doSomething(settings) {
    var preventUndo = settings.hasOwnProperty('preventUndo') ? settings.preventUndo : false;
    // Deal with preventUndo in the normal way.
}

Następnie zadzwoń za pomocą:

doSomething({preventUndo: true});

HA !!! Oboje stworzyliśmy doSomething(x)metodę! lol ... Do tej pory nawet nie zauważyłem twojego postu.
blesh

Co by się stało, gdybym przekazał ciąg „noUndo” i jak jasne jest dla osoby używającej twojego podpisu, jakie ustawienia powinien zawierać bez sprawdzania implementacji?
Nick B.

1
Udokumentuj swój kod. Tak robią wszyscy inni. Ułatwia to odczytywanie kodu, a pisanie go musi nastąpić tylko raz.
ridecar2,

1
@NickB. konwencja jest potężna. Jeśli większość kodu akceptuje obiekty z argumentami, jest to jasne.
orip

4

Uwielbiam wykorzystywać funkcje językowe, aby poprawić sobie przejrzystość. Na przykład w C # można określić parametry według nazwy:

 CallSomething(name: "So and so", age: 12, description: "A random person.");

W JavaScript zazwyczaj wolę użyć obiektu opcji jako argumentu z tego powodu:

function doSomething(args) { /*...*/ }

doSomething({ name: 'So and so', age: 12, description: 'A random person.' });

Ale to tylko moje preferencje. Przypuszczam, że będzie to w dużej mierze zależeć od wywoływanej metody i tego, w jaki sposób IDE pomaga deweloperowi zrozumieć podpis.


1
To „minus” luźno pisanych języków. Nie można tak naprawdę wymusić podpisu bez pewnej weryfikacji. Nie wiedziałem, że możesz dodać takie nazwy w JS.
James P.

1
@JamesPoulson: Prawdopodobnie wiedziałeś, że możesz to zrobić w JS i po prostu nie zdawałeś sobie z tego sprawy. W JQuery: $.ajax({url:'', data:''})itd. Wszystko się skończyło
blesh

Prawdziwe. Na pierwszy rzut oka wydaje się to niezwykłe, ale jest podobne do wbudowanych pseudo obiektów, które mogą istnieć w niektórych językach.
James P.

3

Uważam, że jest to najprostszy i najłatwiejszy do odczytania:

enum Undo { ALLOW, PREVENT }

doSomething(Undo u) {
    if (Undo.ALLOW == u) {
        // save stuff to undo later.
    }
    // do your thing
}

doSomething(Undo.ALLOW);

MainMa tego nie lubi. Być może będziemy musieli zgodzić się nie zgadzać lub możemy skorzystać z rozwiązania, które proponuje Joshua Bloch:

enum Undo {
    ALLOW {
        @Override
        public void createUndoBuffer() {
            // put undo code here
        }
    },
    PREVENT {
        @Override
        public void createUndoBuffer() {
            // do nothing
        }
    };

    public abstract void createUndoBuffer();
}

doSomething(Undo u) {
    u.createUndoBuffer();
    // do your thing
}

Teraz, jeśli kiedykolwiek dodasz Undo.LIMITED_UNDO, Twój kod nie zostanie skompilowany, chyba że zaimplementujesz metodę createUndoBuffer (). A w doSomething () nie ma opcji if (Undo.ALLOW == u). Zrobiłem to na dwa sposoby, a druga metoda jest dość ciężka i trochę trudna do zrozumienia, gdy wyliczanie Undo rozszerza się na strony i strony kodu, ale sprawia, że ​​myślisz. Zwykle trzymam się prostszej metody, aby zastąpić prosty boolean wyliczeniem 2-wartościowym, dopóki nie mam powodu, aby to zmienić. Kiedy dodam trzecią wartość, używam mojego IDE do „Znajdź-zastosowania” i wtedy wszystko naprawiam.


2

Myślę, że twoje rozwiązanie sprawia, że ​​twój kod jest trochę bardziej czytelny, ale unikałbym definiowania dodatkowej zmiennej, aby wyjaśnić twoje wywołania funkcji. Ponadto, jeśli chcesz użyć dodatkowej zmiennej, oznaczę ją jako stałą, jeśli Twój język programowania ją obsługuje.

Mogę wymyślić dwie alternatywy, które nie wymagają dodatkowej zmiennej:

1. Użyj dodatkowego komentarza

doSomething(/* preventUndo */ true);

2. Użyj dwumocnego wyliczenia zamiast wartości logicznej

enum Options
{
    PreventUndo,
    ...
}

...

doSomething(PreventUndo);

Możesz użyć alternatywy 2, jeśli twój język obsługuje wyliczenia.

EDYTOWAĆ

Oczywiście użycie nazwanych argumentów jest również opcją, jeśli twój język je obsługuje.

Jeśli chodzi o dodatkowe kodowanie wymagane przez wyliczenia, wydaje mi się to naprawdę nieistotne. Zamiast

if (preventUndo)
{
    ...
}

ty masz

if (undoOption == PreventUndo)
{
    ...
}

lub

switch (undoOption)
{
  case PreventUndo:
  ...
}

I nawet jeśli jest to trochę więcej pisania, pamiętaj, że kod jest pisany raz i czytany wiele razy, więc warto teraz pisać trochę więcej, aby znaleźć bardziej czytelny kod sześć miesięcy później.


2

Zgadzam się z tym, co powiedział @GlenPeterson:

doSomething(Undo.ALLOW); // call using a self evident enum

ale również interesujące byłyby następujące, ponieważ wydaje mi się, że istnieją tylko dwie możliwości (prawda lub fałsz)

//Two methods    

doSomething()  // this method does something but doesn't prevent undo

doSomethingPreventUndo() // this method does something and prevents undo

2
Fajny pomysł - nie myślałem o tym. Kiedy stajesz się bardziej skomplikowany jak doSomething (String, String, String, boolean, boolean, boolean), wówczas nazwane stałe są jedynym rozsądnym rozwiązaniem. Josh Bloch sugeruje obiekt fabryki i konstruktora jak w: MyThingMaker thingMaker = MyThingMaker.new (); thingMaker.setFirstString („Hi”); thingMaker.setSecondString („There”); itp ... Rzecz t = thingMaker.makeIt (); t.doSomething (); To po prostu dużo więcej pracy, więc lepiej być tego warta.
GlenPeterson

Jednym z problemów związanych z tym drugim podejściem jest to, że niewygodne jest pisanie metody, która używa, doSomethingale umożliwia rozmówcy wybór, czy zezwolić na cofnięcie. Rozwiązaniem może być przeciążenie, które przyjmuje opcję Boolean, ale także wersje otoki, które wywołują poprzednią wersję z parametrem zawsze true lub zawsze false.
supercat

@GlenPeterson Fabryka jest możliwa i jest możliwa w Javascript (wspomniana przez OP).
James P.

2

Ponieważ pytasz głównie o javascript, używając coffeescript możesz mieć nazwany argument, taki jak składnia, super łatwe:

# declare method, ={} declares a default value to prevent null reference errors
doSomething = ({preventUndo} = {}) ->
  if preventUndo
    undo = no
  else
    undo = yes

#call method
doSomething preventUndo: yes
#or 
doSomething(preventUndo: yes)

kompiluje się do

var doSomething;

doSomething = function(_arg) {
  var preventUndo, undo;
  preventUndo = (_arg != null ? _arg : {}).preventUndo;
  if (preventUndo) {
    return undo = false;
  } else {
    return undo = true;
  }
};

doSomething({
  preventUndo: true
});

doSomething({
  preventUndo: true
});

Baw się dobrze na http://js2coffee.org/, aby wypróbować możliwości


Używam coffeeScript tylko wtedy, gdy muszę uniknąć szaleństwa OOP javascript. To miłe rozwiązanie, którego mogę użyć podczas samodzielnego kodowania. Trudno byłoby usprawiedliwić współpracownikowi, że przekazuję obiekt zamiast wartości logicznej ze względu na czytelność!
methodofaction

1

Tylko dla mniej konwencjonalnego rozwiązania, a ponieważ OP wspomniał o Javascript, istnieje możliwość użycia tablicy asocjacyjnej do mapowania wartości. Przewaga nad pojedynczą wartością logiczną polega na tym, że możesz mieć tyle argumentów, ile chcesz i nie martwić się o ich kolejność.

var doSomethingOptions = new Array();

doSomethingOptions["PREVENTUNDO"] = true;
doSomethingOptions["TESTMODE"] = false;

doSomething( doSomethingOptions );

// ...

function doSomething( doSomethingOptions ){
    // Check for null here
    if( doSomethingOptions["PREVENTUNDO"] ) // ...
}

Zdaję sobie sprawę, że jest to odruch kogoś z silnie napisanego tła i może nie jest tak praktyczny, ale rozważ to z oryginalnością.

Inną możliwością jest posiadanie obiektu i jest to również możliwe w JavaScript. Nie jestem w 100% pewien, że jest to poprawna składnia. Zapoznaj się z wzorami, takimi jak Factory, aby uzyskać dalsze inspiracje.

function SomethingDoer( preventUndo ) {
    this.preventUndo = preventUndo;
    this.doSomething = function() {
            if( this.preventUndo ){
                    // ...
            }
    };
}

mySomethingDoer = new SomethingDoer(true).doSomething();

1
Myślę, że lepiej to zapisać w notacji kropkowej ( doSomethingOptions.PREVENTUNDO) zamiast notacji dostępu do tablicy.
Paŭlo Ebermann

1

Głównym celem twojego pytania i innych odpowiedzi jest poprawa czytelności tam, gdzie wywoływana jest funkcja, co jest przedmiotem, z którym się zgadzam. Wszelkie szczegółowe wytyczne, wydarzenie „bez argumentów logicznych”, powinny zawsze funkcjonować jako środki do osiągnięcia tego celu, a nie same stawać się i kończyć.

Myślę, że warto zauważyć, że ten problem można w większości uniknąć, gdy język programowania obsługuje nazwane argumenty / argumenty słów kluczowych, jak w C # 4 i Python, lub gdy argumenty metody są przeplatane w nazwie metody, jak w Smalltalk lub Objective-C.

Przykłady:

// C# 4
foo.doSomething(preventUndo: true);
# Python
foo.doSomething(preventUndo=True)
// Objective-C
[foo doSomethingWith:bar shouldPreventUndo:YES];

0

Należy unikać przekazywania zmiennych boolowskich jako argumentów do funkcji. Ponieważ funkcje powinny robić jedną rzecz na raz. Przekazując zmienną logiczną funkcja ma dwa zachowania. Powoduje to także problem z czytelnością na późniejszym etapie lub dla innych programistów, którzy mają tendencję do zobaczenia twojego kodu. Stwarza to również problem podczas testowania funkcji. Prawdopodobnie w tym przypadku musisz utworzyć dwa przypadki testowe. Wyobraź sobie, że jeśli masz funkcję, która ma instrukcję switch i zmienia swoje zachowanie w zależności od typu przełącznika, musisz zdefiniować dla niej tak wiele różnych przypadków testowych.

Ilekroć ktoś napotka argument logiczny dla funkcji, musi tak dostosować kod, aby w ogóle nie zapisywał argumentu logicznego.


Więc przekształciłbyś funkcję z argumentem logicznym w dwie funkcje, które różnią się tylko niewielką częścią ich kodu (gdzie miałby to oryginał if(param) {...} else {...})?
Paŭlo Ebermann

-1
int preventUndo = true;
doSomething(preventUndo);

Dobry kompilator dla języków słabo rozwiniętych, takich jak C ++, zoptymalizuje kod maszynowy powyżej 2 linii, jak poniżej:

doSomething(true); 

więc nie ma znaczenia wydajność, ale zwiększy czytelność.

Pomocne jest również użycie zmiennej, na wypadek, gdyby stała się zmienna później i może zaakceptować również inne wartości.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.