Methoden mit bool-Parametern und "Lesbarkeit" im Kontext
-
Mir fiel kein wirklich guter Titel für das Thema ein..es geht um folgendes:
ich habe oftmals Parameter welche vom Typ bool sind, z.B. ob Vollbild verwendet werden soll oder Fenstermodus. Mit einem Parameter bool useFullscreen ist die Beudeutung des Parameters klar, wenn man die Parameterliste der Methode anschaut.
Aber wird die Methode irgendwo im Programm aufgerufen so liest man dann nur ein window.setMode(true) und ohne den Namen des Parameters zu kennen lässt sich damit wenig anfangen.
Bei solchen Fällen habe ich schon oft darüber nachgedacht ein enum zu benutzen, welches zwei Symbole hat mit denen man auch im Kontext die Bedeutung feststellen kann.
Für das Beispiel:enum WindowMode { windowed = 0, fullscreen = 1 }; void setMode( WindowMode mode ); //Aufruf dann window.setMode( windowed ); window.setMode( fullscreen );Wie handhabt ihr das? Ich habe bisher meist trotzdem die bool Parameter verwendet, aber in einigen Fällen auch schonmal ein enum benutzt.
-
Statt
setMode(bool useFullscreen)
solltest Du Deine Methode einfach
setFullscreen(bool useFullscreen)
nennen.
Das zentrale Problem, das Du hier hast, ist nämlich, dass Dein Methodenname nichtssagend ist.
-
schau dir vielleicht mal Boost.Parameter an. (Schade das es so was in C++ nicht builtin gibt :()
Gregor schrieb:
Das zentrale Problem, das Du hier hast, ist nämlich, dass Dein Methodenname nichtssagend ist.
Das definitiv
Aber es gibt ja oft den Fall, wo man aus den Parametern nicht mehr so leicht schlau wird.
-
das mit dem enum ist besser, oder du benennst die funktion so um, dass ein 'true' oder 'false' keine fragen aufkommen lässt, also z.b. bei window.setVisible(...) sieht jeder sofort, was true und false bewirken.

-
Boost.Parameter ist ja mal wirklich cool

Mein Minimalbeispiel scheint die Mehrheit eher zu irritieren, schade.
Dass man bei einer einparametrigen Funktion mit einem Bool-Parameter bei passendem Namen keine Verwirrung stiften kann hab ich als gegeben Vorausgesetzt. Es geht um methoden mit mehreren Parametern, wie beim einem Beispiel aus Boost.Parameter:
window w = new_window("alert", 1, true, false);*Ergänzung:
Habe mir zwischenzeitlich die Dokumentation zu Boost.Parameter angesehen und die Anwendung davon ist ja wirklich gelungen, aber dafür sind die Methoden-Deklarationen fast unlesbar und Tools wie Doxygen dürften da ernsthafte Probleme bekommen. Aber ich hab die Dokumentation nur überflogen, werde sie mir später mal komplett durchlesen und das ganze Testen.
-
lolz schrieb:
Mir fiel kein wirklich guter Titel für das Thema ein..es geht um folgendes:
ich habe oftmals Parameter welche vom Typ bool sind, z.B. ob Vollbild verwendet werden soll oder Fenstermodus. Mit einem Parameter bool useFullscreen ist die Beudeutung des Parameters klar, wenn man die Parameterliste der Methode anschaut.
Aber wird die Methode irgendwo im Programm aufgerufen so liest man dann nur ein window.setMode(true) und ohne den Namen des Parameters zu kennen lässt sich damit wenig anfangen.
Bei solchen Fällen habe ich schon oft darüber nachgedacht ein enum zu benutzen, welches zwei Symbole hat mit denen man auch im Kontext die Bedeutung feststellen kann.Wie handhabt ihr das? Ich habe bisher meist trotzdem die bool Parameter verwendet, aber in einigen Fällen auch schonmal ein enum benutzt.
In C nehme ich ein integer und definiere Flags.
#define USE_FULLSCREEN ( 1 << 0 ) #define USE_BORDERS ( 1 << 1 ) if( myStruct.Flags & USE_FULLSCREEN ) ...In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.
class MyClassFlags { private: unsigned int Flags; protected: MyClassFlags( unsigned int flags ) : Flags( flags ) {} public: static MyClassFlags const UseFullscreen; static MyClassFlags const UseBorders; MyStructFlags & operator |= ( MyStructFlags & addFlags ) { return ; MyStructFlags & operator & ( MyStructFlags & checkFlags ); <...> }; class MyClass : public MyClassFlags {}; MyClass myClass; myClass |= myClassFlags::UseFullscreen; if( myClass & myClassFlags::UseFullScreen ) ...Die Lösung hat jedoch den Nachteil, dass man damit unter Umständen Abhängigkeiten aufbauen kann, die in C++ nicht mehr beschreibbar sind. Die Funktion des Programms wird damit abhängig von der Reihenfolge der Objektfiles beim Linkeraufruf.
Um das zu vermeiden ist es u.U. erforderlich Flags über statische Funktionen abzufragen:
MyClassFlags & MyClassFlags::UseFullScreen( void ) { static MyClassFlags result( MyClassFlags::UseFullScreenId ); return result; } <...> myClass |= myClassFlags::UseFullscreen(); if( myClass & myClassFlags::UseFullScreen() ) ...
-
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

-
Undertaker schrieb:
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.
Frage ich USE_FULLSCREEN auf ein anderenObjektyp->Flags ab, kommt ein Ergebnis dabei rum, dass aber u.U. gar nicht so gewünscht ist. Gibt es mehrere Klassen, kann man sich schnell mal vertun: Ist BUTTON_USE_BORDER != WINDOW_USE_BORDER und man vertut sich, kann man stundenlang USE_BORDER lesen, ohne zu bemerken, dass ein BUTTON kein WINDOW ist.
Der Mehraufwand für viele Klassen rentiert sich sobald man den ersten Fehler gesucht hat.
Noch wichtiger: es gibt die Sicherheit, dass man keinen derartigen Fehler übersehen haben kann.
Je weniger Fehler ein Programm haben kann, desto weniger muss ich bedenken, um einen Fehler zu finden.
-
Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?
class MeinEnum { public: static MeinEnum modiEins = new MeinEnum(1); static MeinEnum modiZwo = new MeinEnum(2); static MeinEnum modiDrei = new MeinEnum(3); protected: MeinEnum(int modi) { this->modi = modi; } private: int modi; } // ... { barMethod(MeinEnum::modiEins); }Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool. Man muss dann einfach nur die Methode richtig bennen: setModal() oder setVisible()
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
-
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
Nicht wirklich:
enum T { bla, blup }; void foo(int mode) { } foo(7);
-
Xin schrieb:
Undertaker schrieb:
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.
ach so, deshalb.

-
DEvent schrieb:
Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?
Ich programmiere C++.

DEvent schrieb:
Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool.
Yupp, aber trotzdem kann ein Flag helfen.
OpenWindow( "Titel", WindowFlag::FullScreen );liest sich einfach besser als
OpenWindow( "Titel", true );und macht Kommentierung überflüssig.
OpenWindow( "Titel", /* Fullscreen? */ true );DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
enums sind in C++ einfach ints und befinden sich nichtmals in einem eigenen Namensraum. Man könnte genauso gut #defines nehmen.
In der Beziehung schaue ich leicht neidisch in Richtung C#.
-
OpenWindow (title = "Titel", fullscreen = true);Ist aber schon noch um einiges cooler

Xin schrieb:
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
[...]Man könnte genauso gut #defines nehmen.
Das stimmt so nicht.
enum A { a, b }; enum B { x, y }; void foo (A x) {} int main () { foo (0); foo (x); }liefert folgende wunderschöne Compilerfehler:
test.cpp: In function 'int main()': test.cpp:9: error: invalid conversion from 'int' to 'A' test.cpp:9: error: initializing argument 1 of 'void foo(A)' test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.
-
.filmor schrieb:
Xin schrieb:
[...]Man könnte genauso gut #defines nehmen.
Das stimmt so nicht.
enum A { a, b }; enum B { x, y }; void foo (A x) {} int main () { foo (0); foo (x); }liefert folgende wunderschöne Compilerfehler:
test.cpp: In function 'int main()': test.cpp:9: error: invalid conversion from 'int' to 'A' test.cpp:9: error: initializing argument 1 of 'void foo(A)' test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.
Wow, das muss ich morgen nochmal ausprobieren. Als ich das letzte Mal enum nutzte, wurden die einfach als int verbraten. Darum habe ich sie eigentlich seitdem ignoriert.
-
Xin schrieb:
Ich programmiere C++.

dachte ich haette ein C++ Beispiel gebracht?

-
@Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik

@Shade:
Shade Of Mine schrieb:
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
Nicht wirklich:
enum T { bla, blup }; void foo(int mode) { } foo(7);Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf
void foo(T mode), wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).
-
CStoll schrieb:
Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf
void foo(T mode), wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).enum T { bla, blup }; void foo(T mode) { } foo(bla | blup); //errorman ist auf int festgelegt wenn man enums sinnvoll verwenden will. in ganz wenig ausnahmefällen kann man ein foo(T mode) machen, aber in den meisten Fällen braucht man int - denn wozu flags wenn ich sie nicht kombinieren kann?
-
Also eine Lösung, die das Problem löst, wird wohl schwieriger sein. Entweder du hast eine implizite Umwandlung von int nach Flag (wie bei C enum's), dann kann der Compiler aber nicht sicherstellen, daß die übergebenen int's legal sind - oder du hast keine solche Umwandlung (wie bei C++ enum's), dann funktionieren diese Flag-Kombinationen üblicherweise nicht. Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.
*grübelt* enum's sind doch eigenständige Typen - also wäre es auch erlaubt, die Bit-Operatoren für einen eigenen enum zu überladen:
enum Flag {...}; Flag operator|(Flag r, Flag l) { return (Flag)((int)r|(int)l); } ...
-
Jetzt noch den einen Slash durch ein Klammer auf ersetzt und es würde tatsächlich gehen, ja.
-
CStoll schrieb:
@Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik

C++ kann die Typen WindowFlags und ButtonFlags problemlos unterscheiden.
Und da WindowFlags keinen Operator & anbietet, der auf ButtonFlags reagiert, wird der Compiler meckern.CStoll schrieb:
Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.
Was spricht dagegen die Klasse als Basis zu nutzen. Klappt hier wunderbar.
Die Klasse benötige ich nur, um WindowFlags von Windows zu unterscheiden und WindowFlags von integers, wenn ich Operatoren überlade.