För många systemutvecklare är det jag nu tänker skriva om något vedertaget – bok efter bok förklarar varför man inte ska ägna sig åt det – men för andra verkar det inte vara det vilket ligger till grund för detta inlägg. Det jag pratar om är det där med att peta in setters på attribut i sina klasser bara för att man kan, något som enligt mig (och andra) i de flesta fall är snudd på kriminellt beteende. I vissa avseenden kan det väl vara ok – jag tänker då på primitiva databärare som enkelt ska kunna serialiseras och deserialiseras – men dessa ska ses som undantagsfall.
Varför? Jo, därför att man skapar en state machine från helvetet. Vad menar du nu? Jo, ponera följande klassdefinition. Ni får försöka låta bli att irritera er på den något retarderade klassen och dess attribut – exemplet syftar bara som underlag för att få fram min poäng.
public class Car { private String regNumber; private String model; private Owner owner; private Date ownerRegisteredDate; }
Låt oss säga att vi slänger in publika getters och setters på alla attribut. Om vi nu tänker oss att varje attribut har två möjliga tillstånd – satt eller icke satt – kan vi räkna ut totalt antal möjliga tillstånd som instanser av klassen kan befinna sig i. Med endast fyra attribut landar vi på 2*2*2*2 = 16 möjliga tillstånd. Och nu snackar vi bara om huruvida attributen är satta eller ej. Tänk er att vi kastar in ett attribut till – en enum med fem möjliga tillstånd – och vi landar på 16*5 = 80 möjliga tillstånd. Ja ni ser vart jag är på väg.
Nu pratar jag dessutom bara om möjliga tillstånd vilket inte är samma sak som giltiga tillstånd. Är tillståndet i en instans giltigt om alla attribut har värdet null? Skulle man kunna tänka sig att det alltid bör finnas ett regNumber på en bil? Borde det inte alltid finnas ett ownerRegisteredDate om owner är satt?
Med setters på allt kan man inte dra sådana slutsatser. Designen begränsar därmed domänmodellens potential och man degraderar dess klasser till beteendelösa databärare vars tillstånd man underhåller med logik i servicelager och validatorer. Hela grejen är bara så fel.
I synnerhet som det finns så enkla lösningar med vilka man kan komma ganska långt.
Låt oss säga att ovanstående klass alltid måste ha ett regNumber satt. När en ny bil registreras måste man dessutom ange model. Det finns inga krav på att model någonsin ska ändras. Låt då bli att lägga dit en setter! Vidare kan en bil ha en ägare (owner) med ett startdatum för ägandeskapet (ownerRegisteredDate). Dessa kan komma att ändras över tid. Här behöver vi någon konstruktion som låter oss sätta dessa värden. Vi tittar på hur en enligt mig mer sund klassdefinition skulle kunna se ut:
public class Car { private String regNumber; private String model; private Optional<Owner> owner; private Optional<Date> ownerRegisteredDate; public Car(String regNumber, String model) { this.regNumber = checkNotNull(regNumber); this.model = checkNotNull(model); } public void registerOwner(Owner owner) { this.owner = Optional.of(checkNotNull(owner)); this.ownerRegisteredDate = Optional.of(new Date()); } public void deregisterOwner() { this.owner = Optional.<Owner>absent(); this.ownerRegisteredDate = Optional.<Date>absent(); } }
Det ska tilläggas att jag i ovan exempel använder Preconditions från Guava och Optional från Guava/Java 8 för att få koden mer läsbar. Koden är dessutom skriven på frihand så jag reserverar mig för syntaxfel. Jag har även utelämnat eventuella getters. Men min poäng är ändå att vi här endast har två möjliga tillstånd för instanser av denna klass. För att förtydliga detta tittar vi i nedanstående tabell. Varje rad anger ett tillstånd och x markerar att attributet har ett värde.
regNumber | model | owner | ownerRegisteredDate |
---|---|---|---|
x | x | ||
x | x | x | x |
Vi går alltså från 16 möjliga tillstånd till endast två. Dessutom är dessa två tillstånd även giltiga. Vi vet att regNumber och model alltid har ett värde och att de aldrig kommer att ändras. Vi vet också att owner och ownerRegisteredDate endast kan existera och ändras tillsammans. Med enkla medel och avgränsningar på rätt ställe kommer man väldigt långt.
För den intresserade vill jag återigen lobba för Eric Evans bok Domain-Driven Design: Tackling Complexity in the Heart of Software. Alla borde läsa den.
2 svar till “Setters under ansvar”
Jag håller helt med dig här. Till viss del så vill jag skylla lite på C# här då de gjort det så enkelt med sina auto-properties och sin objectskapnings-syntax så att man kan göra saker i stil med
public class Foo {
public string Bar { get; set; }
public int Baz { get; set; }
}
var foo = new Foo { Bar = "apa", Baz = 42 };
Tror att många programmerare ser att man kan göra såhär och tänker ”vad skönt! jag behöver inte skriva en massa konstruktorer!” och sedan så kör man vidare med det implicita kontraktet att ”Bar ska alltid vara satt och ska aldrig ändras efter objektskapandet” ända tills en ny programmerare dyker upp som inte vet om detta eller man avserialiserar något objekt snett någonstans eller något annat av alla de hundratals saker som kan hända. ..
Jag hade själv lite av det problemet ett tag när jag började hacka C# tills jag blev biten i häcken av det en gång för mycket… Numera så älskar jag mina konstruktorer och är även väldigt noggrann med vilka interface jag exponerar utåt (behöver jag verkligen exponera IList här eller räcker det med IEnumerable?). Jag känner att min kod har blivit betydligt bättre och felfri av detta, även om jag ibland får skriva betydligt mer kod.
Problemet dyker dock upp när man har att göra med bibliotek/ramverk som kräver att man har parameterlösa konstruktorer och publika setters (json-serialisering, databas-objekt osv…) Här har jag dock börjat underhålla separata objekt för dessa ändamål med metoder på dem för att transformera dem till mina trevliga säkra business objects med vettiga konstruktorer och interface utåt. Det blir ännu mer kod att skriva, vilket kan kännas jävligt surt ibland, men oftast känns det värt det.
Här är en länk till ett intressant föredrag från NDC om detta ämne som jag tyckte var rätt träffande 🙂
Gött att vi har samma tankar! Visst har det gjorts väl enkelt i C# men i och med JavaBeans-standarden har Java också en del i skulden. Om man ska förhålla sig till den standarden ska man ha en konstruktor utan argument och getters och setters på allt. Tanken med att kompromissa för att göra tekniska ramverk nöjda när man modellerar domänmodellen känns inte okej i min hjärna. Extra kod till trots tror jag som du att man vinner på att kunna modellera sina business objects som du kallar dem med alla de mekanismer som objektorienteringen erbjuder. Tack för din kommentar, koda på! 🙂