Scriptorama.nl

Header image showing a keyboard, mouse, laptop and books on design patterns

Duidelijkere code schrijven met Hungarian Notation

PHP is een zogenaamde loosely typed taal. Dit houdt in dat de PHP engine het in principe niet uitmaakt welke variabele welke waarde bevat. Je kunt ze door elkaar gebruiken. Wijs een integer toe aan een variabele die eerst een string was of overschrijf een diepe object tree met een integer, maakt allemaal niet uit. PHP staat het toe.

Aan de ene kant is het een mooi idee en is het een van de redenen waarom PHP zo aanslaat: je kunt snel en simpel iets realiseren zonder dat je op iedere hoek door PHP op je vingers getikt wordt. Op het moment dat je serieus dingen gaat bouwen zou iets meer controle wel fijn zijn want je maakt snel fouten op deze manier.

Een van de dingen die de wat gevorderde programmeurs dan vaak doen is zich richten op de zogenaamde hungarian notation bij hun variabelen. Voor veel mensen is de hungarian notation een variabele naam notatie waaraan je kunt zien wat voor type die betreffende variabele is.

Een voorbeeld:

PHP:
  1. <?php
  2.  
  3. $iLeeftijd = 15;
  4. $oRenderImpl = RenderFactory::create('HTML');
  5.  
  6. ?>

Als je $iLeeftijd leest, met de i van integer, weet je dus dat het gaat om een integer. In het geval van $oRenderImpl, met de o van object, weet je dat het gaat om een instantie van een bepaalde klasse. So far so good. Alleen is het nut van deze notatie discutabel. Bij beide voorbeelden kan iemand die een beetje nadenkt inzien wat voor types deze variabelen zouden moeten hebben.

In sommige gevallen kan het zelfs compleet nutteloos zijn; stel dat je een unicode string wilt definiëren. Het gaat hier dan om een string, dus gebruik je een s:

PHP:
  1. $sNaam = utf8_encode('Mijn naam in utf8');
  2.  
  3. echo strlen($sNaam);

Je kunt nu op geen enkele manier zien aan $sNaam dat het hier gaat om een unicode string. De code die hier staat is volledig correct volgens PHP maar als je vervolgens om de lengte van de string vraagt met de gewone strlen() functie, kan het zijn dat je het foute resultaat krijgt. strlen() telt namelijk alleen het aantal bytes in de opgegeven variabele en niet het aantal karakters. Een unicode karakters kan uit 1 tot 4 bytes bestaan.

Dit probleem is wat Joel Spolsky bespreekt in zijn interessante artikel Making wrong code look wrong. Volgens het artikel kun je, door de hungarian notation op de manier te gebruiken zoals deze door de bedenker, Charles Simonyi, bedoeld was, door enkel de variabele namen te lezen inzien dat bepaalde code functioneel onjuist is.

If you read Simonyi’s paper closely, what he was getting at was the same kind of naming convention as I used in my example above where we decided that us meant “unsafe string” and s meant “safe string.” They’re both of type string. The compiler won’t help you if you assign one to the other and Intellisense won’t tell you bupkis. But they are semantically different; they need to be interpreted differently and treated differently and some kind of conversion function will need to be called if you assign one to the other or you will have a runtime bug. If you’re lucky.

Door een functionelere omschrijving (prefix) te geven aan je variabelen kun je dus een duidelijker beeld scheppen van wat wel en niet de bedoeling is met die variabele.

PHP:
  1. $uniNaam = utf8_encode('Mijn naam in utf8');
  2.  
  3. echo strlen($uniNaam);

In dit voorbeeld gebruiken we de prefix 'uni' om aan te geven dat het hier gaat om een unicode string. Op deze wijze kun je vrijwel direct zien dat het geven van een variabele met een prefix van 'uni' aan de strlen() functie niet slim is.

Dit biedt natuurlijk wat mogelijkheden. In Tips voor een veiligere website gebruik ik bijvoorbeeld de prefix 'onveilig' maar ook de suffix Html en Sql om aan te geven in wat voor context deze variabelen wel of niet gebruikt mogen worden. Nu zijn deze complete woorden niet bijzonder praktisch. Spolsky zelf geeft al aan dat je de prefix 'us' van Unsafe String kunt gebruiken voor gegevens die niet zonder eerst gecontroleerd te zijn naar de browser gestuurd mogen worden. Aangezien security iets verder gaat dan alleen cross-site-scripting is het misschien juist wel slim om een iets uitgebreidere set prefixen te gebruiken hiervoor:

  • usp: Unsafe SQL Parameter - voor het gebruik van waarden die uit bijvoorbeeld $_GET en $_POST komen die in een SQL query verwerkt moeten worden.
  • usv: Unsafe Shell Value: - voor het gebruik van $_GET/$_POST/$_COOKIE waarden die in een commandline string voor shell_exec() verwerkt moeten worden.
  • uhv: Unsafe HTML value - voor het gebruik van $_GET/$_POST/$_COOKIE waarden die uiteindelijk in HTML output gebruikt moeten worden.

Om aan te geven dat iets wel veilig is zou je natuurlijk respectievelijk 'ssp', 'ssv' en 'shv' kunnen gebruiken, waarbij 's' van 'Safe' de 'u' vervangt.

PHP:
  1. // onjuist: een Unsafe SQL parameter wordt direct gebruikt in een SQL query.
  2.  
  3. $uspNaam  = $_GET['naam'];
  4. $sqlQuery = "DELETE FROM users WHERE naam = $uspNaam";
  5.  
  6. // juist: aan een Safe Sql Parameter wordt een gecontroleerde waarde van $_GET['naam'] toegewezen
  7. // welke vervolgens veilig in een SQL query gebruikt kan worden.
  8.  
  9. $sspNaam  = mysql_real_escape_string($_GET['naam']);
  10. $sqlQuery = "DELETE FROM users WHERE naam = '$sspNaam'";

3 letterige prefixen zijn wel wat aan de lange kant, dus misschien zou het inkorten tot 2 niet zo'n gek idee zijn.

Spolsky zelf geeft nog een paar andere voorbeelden van prefixen die je zou kunnen gebruiken:

  • cb: Count of Bytes - Het aantal bytes in een variabele
  • ix: Index - Bevat een array index
  • rw: Row - bevat een database row.

Iemand nog andere ideeen voor handige prefixen? ;-)

Reageer ook!

Ik vind dit een beetje overdaad. True, zo ga je _mogelijk_ zien dat het fout is als je een 'usp' rechtstreeks in je query gebruikt, maar het feit dat je zo netjes script wil ook al wat zeggen over je scriptervaring. Therefore weet je dat je zo'n zaken niet mag doen en dus giet je er (waar het nodig is) addslashes/mysql_real_escape_string/nog_een_andere_functie over.

Een eerder beginnend php'er geraakt hierdoor in de war geraken/wil het nadoen (en doet dit ook verkeerd) en zo krijg je heel verwarrende situaties.

Ik denk dat als je een safe en een unsafe uit elkaar kan houden (bij het definiëren en aanwijzen van) dan weet je ook wel wat je hiermee moet doen (zonder zo'n gekke notatie).

Ik begrijp niet in welk geval je ix en rw moet gaan gebruiken. Toch niet bij foreach of een dergelijke? (want daar doen $key en $value (of $k en $v) het IMO wel goed)

Offcourse, je kan er voorhangen wat je wil, zolang het maar duidelijk is voor jezelf en derden die het script zullen moeten doorlezen/aanpassen.

IMO kan het zelfs goed zijn om massa's lange uitleg (langer dan 3 tekens bedoel ik hier) voor (of na) je string te gooien, zolang het maar duidelijk blijft en je niet willekeurig met letters begint te schieten.

"Iemand nog andere ideeen voor handige prefixen? ;-)"

Mja, als we dan toch bezig zijn?
'utq' = untested query (kan nog altijd false teruggeven)
'prc' = 'possible resource' (is_resource() ?)

En zo zie je maar weer dat het een beetje overdaad is.

Eerder verwarrend dan echt helpend.

(ik vind overigens 'uni' niet slecht, de rest vind ik over the top)

In _Essential PHP Security_ gebruikt Shiflett het systeem van bvb de array mysql aan te maken en enkel gecontroleerde vars in die array te steken. Als je dan ziet dat er in je query ook vars staan die niet uit de $mysql[] komen is dit een extra waarschuwing om zeker geen verstrooidheidsfouten te maken. Zelf gebruik ik het niet echt maar het is wel een goede manier voor beginnende scripters om een bepaalde "safety-attitude" te kweken.

Therefore weet je dat je zo’n zaken niet mag doen en dus giet je er (waar het nodig is) addslashes/mysql_real_escape_string/nog_een_andere_functie over.

Je werkt natuurlijk niet altijd alleen. Met een dergelijke notatie kun je ook aan je collega's aangeven wat de bedoeling is van een stuk code.

Nu ik er iets verder over nagedacht heb, heb ik inderdaad ook het gevoel dat een dusdanig specifieke prefix overdreven is. Toch staat het idee van op deze wijze een context geven aan de variabele me wel erg aan.

De manier die Brossiekoppie beschrijft is dan ook een erg interessant alternatief.

Inderdaad, de waardes opslaan in een 'safe array' is niet slecht gevonden.

Het lijkt me natuurlijk wel handig dat je een duidelijke omschrijving geeft in de (roep)naam van de variabele.

Vergeet niet dat het bij het Object Georienteerd programmeren vaak handig is om een scope mee te geven.

Bijvoorbeeld:

PHP:
  1. class Voorbeeld
  2. {
  3.  /**
  4.   * settext
  5.   * @param string $p_sText
  6.   * @var string $m_sText
  7.   */
  8.   public function setText($p_sText) {
  9.       if (is_string($p_sText)) {
  10.           $this->m_sText = $p_sText;
  11.       }
  12.   }
  13. }

Hierbij is de $m_sText een member variable en de p_sText een parameter van een method. Hierdoor weet je ook waar de waardes in de script vandaan komen of hoe ze erin gekomen zijn.

Ziet er goed uit me getypte code :). De helft is weg?

Vreemd, mijn notificatie mailtje bevat ook de helft, het deel met m_text. Ik heb je comment wat aangepast zodat het er wat beter uit ziet; mocht ik de code verkeerd aangepast hebben, mail me even; regel ik dat er komt te staan wat je bedoelde ;-)

Om weer over het artikel te hebben, met de 'm_' prefix voor member ben ik het niet zo eens, zeker niet in PHP waar je altijd al $this-> moet gebruiken dat sowieso al voldoende context geeft.

Ook van 'p_' ben ik niet enorm onder de indruk, maar dat is misschien omdat ik altijd zorg dat mijn data rauw (wat inhoudt dat alle beveiligingen altijd nog moeten uitgevoerd worden) is op het moment dat ze aan classes gegeven worden - met de uitzondering van klassen die direct werken met input data.

Ik had ook een stukje code toegevoegd aan mijn vorige reply maar dat is blijkbaar ook er 'afgeknipt'. "$this->;m_sText = $p_sText;" is die 1e ';' de bedoeling of is dat een fout?

"Ook van 'p_' ben ik niet enorm onder de indruk"
Zelfde hier.

Vreemd. Zal eens kijken wat Wordpress nou precies aan het uitspoken is met die comments.

Worpress doet wel meer gekke dingen:s Het is eigenlijk een heel knap systeem (updaten, layout verwisselen in 0 sec, toevoegen, zaken verwijderen, toevoegen aan categorien, ...) maar sommige heel logische zaken werken langs geen kanten/zitten er niet in...

Het gaat hier om een klein voorbeeldje, maar ik doelde er meer op om op deze manier een goede onderscheid te kunnen maken tussen member variabelen en bijvoorbeeld waardes die uit een parameter afkomstig zijn. Voor een kleine klasse is dit niet echt nuttig, maar wanneer je met bijv. inhertance gaat werken lijkt mij dit wel degelijk handig.

Dat begreep ik :).

Wat is volgens jou dan de toegevoegde waarde van het daadwerkelijk weten dat iets een parameter is? En, in hoeverre is $this->m_mijnMember duidelijkers dan $this->mijnMember ?

Wat ik dan wel weer gebruik is $this->_mijnMember, een underscore voor private en protected members.

Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>