Jag hade precis en längre gående disskusion med Johan Normen angående returvärden på felaktiga inparametrar till en funktion. Jag håller med dig, jag anser att ALLA argument ska valideras innan dom används. Johan nämnde säkert att Microsoft returnerar Null om inparametern är Null till deras GetUser metod som finns i klassen MembershipUser för .Net 2.0. Av ren nyfikenhet så frågade Microsoft varför dom valde att göra så. Svaret jag fick var delvis följande: Ditt svar baserades endast på string.empty.Exception vs Null som returvärde från en metod
Metoden i scenariot vi pratade om var följande:
public User GetUser(string UserName);
Metoden förväntar sig ett användarnamn i formen av en sträng för att kunna utföra sin uppgift. Frågeställningen som uppstår är,
* Vad skall den returnera om användaren inte hittas?
* Hur skall man hantera felaktiga inparametrar?
När man arbeter i en objektsorienterad miljö står "usability" högt i fokus, det vill säga att det skall vara enkelt att använda dina klasser och objekt.
När du som programmerare skriver din egen kod själv, är det sällan man behöver fundera på hur andra programmerare skall få information om att något har gått fel. Man känner ju själv till hur saker och ting fungerar. Däremot om klassen som just du bygger sedan skall användas av andra än dig själv, är det nödvändigt att alltid informera om fel som uppstår, detta gör man vanligtvis med Exceptions.
Exceptions är vårt sätt att informera andra programmerare (eller oss själva) att något är fel i koden som exekveras, för mig innebär det att om någon förser min metod med felaktiga inparametrar, så skall de också bli informerade om att de inte gjort rätt.
Funktionen ovan förväntar sig ett användarnamn för att kunna fungera, det innebär att inget användarnamn eller <b>null</b> som inparameter garanterar att funktionen inte kommer att returnera ett korrekt svar. Detta är ett programatiskt fel av den eller de programmerare som använder sig av funktionen, eftersom de inte kan förvänta sig ett ordentligt svar med sådana inparametrar.
De borde kontrollera parametrarna innan de skickar in dem till metoden, därför skall de bli ordentligt varse om att de inte använder funktionen som den var tänkt.
public User GetUser(string UserName)
{
if ( null == UserName || string.Empty == UserName )
throw new ArgumentException("Method GetUser expects a UserName");
}
Vad vi nu har skapat är en så kallad "Constraint" en begränsing. Funktionen behöver ett invärde för att kunna utföra sitta arbete, får den inte det så används metoden fel och ett Exception kastas.
Om den däremot får ett invärde men invärdet visar sig sakna matchande värde i tex en Databas, bör man kasta ett exception då?
Jag hävdar att så inte är fallet, om en inparameter innehåller ett värde så har jag använt metoden rätt oavsett om svaret är vad jag förväntar mig att få. Det innebär att jag i så fall skall returnera <b>null</b> från funktionen eftersom det är exakt vad invärdet representerar, dvs ingenting.
public User GetUser(string UserName)
{
if ( null == UserName || string.Empty == UserName )
throw new ArgumentException("Method GetUser expects a UserName");
.
.
.
if ( ! User.Exists )
return null;
else
return new User(UserName);
}
Sv: Exception vs Null som returvärde från en metod
Om vi anropar metoden GetUser("Kalle"); så förväntar vi oss att hitta kalle, om nu kalle inte hittades så returneras Null. Därför kommer programmeraren till stor sannolikhet kontrollera så att GetUser inte returnerar null:
MembershipUser user = Membership.GetUser("Kalle");
if( user != null )
Så i detta fall om vi skulle skicka in null så har vi en validering på att användaren inte hittades. Null kommer att returneras ifrån GetUser när null skickas in (dock så kommer detta att ändras i Beta 1 av ASP.Net 2.0. Då kommer en exception att slängas om null skickas in som argument). Det går även att skicka in en tom sträng till GetUser. Denna sträng behandlas som om det vore en användare, de anser att det kan finnas dom utvecklarna som vill hantera en tom sträng som en användare. Den strösta orsaken till att de valde att göra på detta sätt är att membership kan användas i icke ASP.Net applikationer (som tex Indigo, Windows Applikationer eller konstol applikationer) där trådens principal kan vara null.
Microsoft valde att returnera en tom sträng från en privat metod som används när enbart GetUser() methoden anropas för att få ut den tillfälligt inloggade användaren. Om Membership inte körs under ASP.Net, samt om Thread.CurrentPrincipal inte är initierat (vilket kan förekomma i andra typer av applikationer än ASP.Net) så returneras en tom sträng, I dessa situationer så kan en utvecklare använda en "" sträng för att representera en användare. Att en tom sträng förekommer i en ASP.Net applikation är inte så troligt. Men att det förekommer i en Windows applikation eller Indigo, är mycket mer troligare.
Dock anser jag, att vi som äger vår egna implementation och vet vad våra metoder ska användas till, så ska vi tänka på att validera våra argument, ska en metod förvänta sig att argument som ska representera ett värde så ska en exception slängas precis som Patrik säger om värdet inte är angivet. I Patriks exempel med GetUser så förväntar den metoden ett användarnamn, och får den inget användar namn så slänger den en exception.
/Fredrik Normén NSQUARED2
http://normen.mine.nu/myblogSv: Exception vs Null som returvärde från en metod
Jag frågade specifikt ang Null och det var pga tidsbrist de inte slängde in en kontroll som slängde en exception. Dock kommer inte string.empty som du säger casta någon exception utan gå genom och det är om jag fattat allt rätt Providerns uppgift att göra något med denna empty string.
Att jag tog upp detta var just pga förvirringen varför MS endast i sin GetUser(string name) metod inte kastade en exception men gjorde det i alla andra.
Det finns andra fall då null som inparameter inte bör kasta en exception och det är exempelvis vid metoder som kan ta in ett objekt men inte kräver objektet utan går på sitt default objekt om inputen är null.
ex:
Foo(someObject)
{
Foo(someObject,null);
}
Foo(SomeObject object,ActionObject object1)
{
if(object1 == null)
this._actionObject = new MyDefaultActionObject();
do stuff...
}
Nu när INullable<t> kommer till C# 2 kommer vi säkerligen få en hel del nya tankar kring nullhantering. Har redan hängt med i en hel del diskusioner ang detta. Men det är ett annat ämne...
Mvh Johan