Skriv kod, som är lätt att ändra, är Programmets Heliga Graal. Välkommen till programmering nirvana! Men saker är mycket svårare i verkligheten: källkoden är svår att förstå, beroende beror i otaliga riktningar, koppling är irriterande, och du känner snart värmen i programmering helvete. I den här handledningen diskuteras några principer, tekniker och idéer som hjälper dig att skriva kod som är lätt att ändra.
Objektorienterad programmering (OOP) blev populär på grund av sitt löfte om kodorganisation och återanvändning. det misslyckades helt i denna strävan. Vi har använt OOP-koncept i många år nu, men vi fortsätter att upprepade gånger genomföra samma logik i våra projekt. OOP införde en uppsättning bra grundläggande principer som, om de används korrekt, kan leda till bättre, renare kod.
De saker som tillhör varandra borde hållas ihop; annars borde de flyttas någon annanstans. Det här är vad termen, sammanhållning, refererar till. Det bästa exemplet på sammanhållning kan demonstreras med en klass:
klass ANOTChesiveClass privat $ firstNumber; privat $ secondNumber; privat $ längd; privat $ bredd; funktion __construct ($ firstNumber, $ secondNumber) $ this-> firstNumber = $ firstNumber; $ this-> secondNumber = $ secondNumber; funktion setLängd ($ längd) $ this-> length = $ length; funktion setHight ($ höjd) $ this-> width = $ height; funktion add () returnera $ this-> firstNumber + $ this-> secondNumber; funktion subtrahera () returnera $ this-> firstNumber - $ this-> secondNumber; funktionsområde () returnera $ this-> längd * $ this-> width;
I det här exemplet definieras en klass med fält som representerar antal och storlekar. Dessa egenskaper, som endast döms av deras namn, hör inte ihop. Vi har då två metoder, Lägg till()
och subtrahera ()
, som fungerar endast på de två talvariablerna. Vi har vidare en område()
metod som fungerar på längd
och bredd
fält.
Det är uppenbart att den här klassen är ansvarig för separata grupper av information. Den har mycket låg sammanhållning. Låt oss refactor det.
klass ACohesiveClass privat $ firstNumber; privat $ secondNumber; funktion __construct ($ firstNumber, $ secondNumber) $ this-> firstNumber = $ firstNumber; $ this-> secondNumber = $ secondNumber; funktion add () returnera $ this-> firstNumber + $ this-> secondNumber; funktion subtrahera () returnera $ this-> firstNumber - $ this-> secondNumber;
Det här är en hög sammanhängande klass. Varför? Eftersom varje del av denna klass hör till varandra. Du bör sträva efter sammanhållning, men akta dig, det kan vara svårt att uppnå.
I enkla termer hänvisar ortogonalitet till isolering eller eliminering av biverkningar. En metod, klass eller modul som ändrar tillståndet för andra orelaterade klasser eller moduler är inte ortogonalt. Till exempel är en flygplans svart låda ortogonal. Den har sin inre funktionalitet, inre strömkälla, mikrofoner och sensorer. Det har ingen inverkan på det flygplan som ligger i eller i den yttre världen. Det ger bara en mekanism för att spela in och hämta flygdata.
Ett exempel på ett sådant icke-ortogonalt system är din bils elektronik. Öka din bils hastighet har flera biverkningar, till exempel ökad radiovolym (bland annat). Hastigheten är inte ortogonal mot bilen.
klasskalkylator privat $ firstNumber; privat $ secondNumber; funktion __construct ($ firstNumber, $ secondNumber) $ this-> firstNumber = $ firstNumber; $ this-> secondNumber = $ secondNumber; funktion add () $ sum = $ this-> firstNumber + $ this-> secondNumber; om ($ sum> 100) (new AlertMechanism ()) -> tooBigNumber ($ sum); returnera $ sum; funktion subtrahera () returnera $ this-> firstNumber - $ this-> secondNumber; klass AlertMechanism funktion tooBigNumber ($ number) echo $ nummer. 'är för stor!';
I det här exemplet är Kalkylator
klassens Lägg till()
Metod uppvisar oväntat beteende: det skapar en AlertMechanism
objekt och kallar en av dess metoder. Detta är oväntat och oönskat beteende; bibliotekskonsumenter kommer aldrig att förvänta sig ett meddelande som skrivs ut på skärmen. I stället förväntar de sig bara summan av de angivna siffrorna.
klasskalkylator privat $ firstNumber; privat $ secondNumber; funktion __construct ($ firstNumber, $ secondNumber) $ this-> firstNumber = $ firstNumber; $ this-> secondNumber = $ secondNumber; funktion add () returnera $ this-> firstNumber + $ this-> secondNumber; funktion subtrahera () returnera $ this-> firstNumber - $ this-> secondNumber; klass AlertMechanism funktion checkLimits ($ firstNumber, $ secondNumber) $ sum = (ny kalkylator ($ firstNumber, $ secondNumber)) -> add (); om ($ sum> 100) $ this-> tooBigNumber ($ sum); funktion tooBigNumber ($ number) echo $ nummer. 'är för stor!';
Detta är bättre. AlertMechanism
har ingen effekt på Kalkylator
. Istället, AlertMechanism
använder vad som helst för att avgöra om en varning ska utfärdas.
I de flesta fall är dessa två ord utbytbara; men i vissa fall föredras en term över en annan.
Så vad är det? beroendet? När objektet en
behöver använda objekt B
, För att kunna utföra sitt föreskrivna beteende säger vi det en
beror på B
. I OOP är beroenden extremt vanliga. Objekt arbetar ofta med och beror på varandra. Så, när du eliminerar beroende är en ädlad strävan, är det nästan omöjligt att göra det. Det är dock föredraget att kontrollera beroenden och minska dem.
Villkoren, tung-koppling och lös-koppling, brukar hänvisa till hur mycket ett objekt beror på andra objekt.
I ett löst kopplat system har ändringar i ett objekt minskad effekt på de andra objekten som är beroende av det. I sådana system beror klasser på gränssnitt istället för konkreta implementeringar (vi kommer att prata mer om det senare). Därför är löstkopplade system mer öppna för modifikationer.
Låt oss överväga ett exempel:
klass Visa privat $ kalkylator; funktion __construct () $ this-> calculator = ny kalkylator (1,2);
Det är vanligt att se den här typen av kod. En klass, Visa
i detta fall beror på Kalkylator
klass genom att direkt hänvisa till den klassen. I ovanstående kod, Visa
's $ kalkylator
fältet är av typ Kalkylator
. Objektet som innehåller fältet är ett resultat av att direkt ringer Kalkylator
konstruktör.
Granska följande kod för en demonstration av denna typ av koppling:
klass Visa privat $ kalkylator; funktion __construct () $ this-> calculator = ny kalkylator (1, 2); funktion printSum () echo $ this-> calculator-> add ();
De Visa
klassen kallar Kalkylator
objekt Lägg till()
metod. Detta är en annan form av koppling, eftersom en klass får tillgång till den andra metoden.
Du kan koppla klasser med metodreferenser också. Till exempel:
klass Visa privat $ kalkylator; funktion __construct () $ this-> calculator = $ this-> makeCalculator (); funktion printSum () echo $ this-> calculator-> add (); funktion makeCalculator () returnera ny kalkylator (1, 2);
Det är viktigt att notera att makeCalculator ()
Metoden returnerar a Kalkylator
objekt. Detta är ett beroende.
Erfarenhet är förmodligen den starkaste formen av beroende:
klass AdvancedCalculator utökar kalkylator funktion sinus ($ värde) return sin ($ value);
Inte bara kan AdvancedCalculator
inte jobba utan Kalkylator
, men det kunde inte ens existera utan det.
Man kan minska kopplingen genom att injicera ett beroende. Här är ett sådant exempel:
klass Visa privat $ kalkylator; funktion __construct (Calculator $ calculator = null) $ this-> calculator = $ calculator? : $ this-> makeCalculator (); // ... //
Genom att injicera Kalkylator
objekt genom Visa
s konstruktör, vi minskade Visa
beroende av Kalkylator
klass. Men det här är bara hälften av lösningen.
Vi kan ytterligare minska kopplingen genom att använda gränssnitt. Till exempel:
gränssnitt CanCompute funktion add (); funktion subtrahera (); klass Calculator implementerar CanCompute privat $ firstNumber; privat $ secondNumber; funktion __construct ($ firstNumber, $ secondNumber) $ this-> firstNumber = $ firstNumber; $ this-> secondNumber = $ secondNumber; funktion add () returnera $ this-> firstNumber + $ this-> secondNumber; funktion subtrahera () returnera $ this-> firstNumber - $ this-> secondNumber; klass Visa privat $ kalkylator; funktion __construct (CanCompute $ calculator = null) $ this-> calculator = $ calculator? : $ this-> makeCalculator (); funktion printSum () echo $ this-> calculator-> add (); funktion makeCalculator () returnera ny kalkylator (1, 2);
Du kan tänka på Internetleverantören som en högre sammanhållningsprincip.
Denna kod introducerar CanCompute
gränssnitt. Ett gränssnitt är lika abstrakt som du kan få i OOP; Det definierar de medlemmar som en klass måste genomföra. I fallet med ovanstående exempel, Kalkylator
implementerar CanCompute
gränssnitt.
Visa
Konstruktör förväntar sig ett objekt som implementerar CanCompute
. Vid denna punkt, Visa
beroende av Kalkylator
är effektivt bruten. Vi kan när som helst skapa en annan klass som implementerar CanCompute
och vidarebefordra ett objekt av den klassen till Visa
konstruktör. Visa
Nu beror bara på CanCompute
gränssnitt, men även det beroendet är valfritt. Om vi inte skickar några argument till Visa
konstruktör, det kommer helt enkelt skapa en klassiker Kalkylator
objekt genom att ringa makeCalculator ()
. Denna teknik används ofta och är extremt användbar för testdriven utveckling (TDD).
SOLID är en uppsättning principer för att skriva ren kod, vilket gör det lättare att ändra, behålla och utöka i framtiden. De är rekommendationer som, när de tillämpas på källkod, har en positiv effekt på hållbarheten.
SOLID-principerna, även kända som Agile-principer, definierades initialt av Robert C. Martin. Trots att han inte uppfann alla dessa principer var han den som satte dem ihop. Du kan läsa mer om dem i sin bok: Agile Software Development, Principles, Patterns, and Practices. SOLIDs principer täcker ett brett spektrum av ämnen, men jag kommer presentera dem så enkelt som möjligt. Fråga dig om ytterligare detaljer i kommentarerna, om det behövs.
En klass har ett enda ansvar. Det låter enkelt, men det kan ibland vara svårt att förstå och genomföra.
klass Reporter funktion generateIncomeReports (); funktion generatePaymentsReports (); funktionen computeBalance (); funktion printReport ();
Vem tycker du om fördelar med detta klassers beteende? Tja, en bokföringsavdelning är ett alternativ (för balansen), finansavdelningen kan vara en annan (för inkomst- / betalningsrapporter), och även arkivavdelningen skulle kunna skriva ut och arkivera rapporterna.
Det finns fyra anledningar till varför du kanske måste ändra den här klassen. varje avdelning kan vilja att deras respektive metoder anpassas efter deras behov.
SRP rekommenderar att sådana klasser bryts i mindre, beahvior-specifika klasser, var och en har bara en anledning att ändra. Sådana klasser tenderar att vara mycket sammanhängande och löst kopplade. På ett sätt definieras SRP sammanhållning ur användarnas synvinkel.
Klasser (och moduler) bör välkomna utvidgningen av deras funktionalitet, samt motstå modifikationer av deras nuvarande funktionalitet. Låt oss spela med det klassiska exemplet på en elfläkt. Du har en strömbrytare och du vill styra fläkten. Så kan du skriva något enligt följande:
klass Switch_ privat $ fan; funktion __construct () $ this-> fan = new Fan (); funktion turnOn () $ this-> fan-> på (); funktion turnOff () $ this-> fan-> off ();
Arv är förmodligen den starkaste formen av beroende.
Denna kod definierar a Växla_
klass som skapar och kontrollerar a Fläkt
objekt. Observera underskriften efter "Switch_". PHP tillåter dig inte att definiera en klass med namnet "Switch".
Din chef bestämmer att han vill styra ljuset med samma strömbrytare. Detta är ett problem, för att du måste byta Växla_
.
Eventuella ändringar av befintlig kod är en risk; Andra delar av systemet kan påverkas och kräva ytterligare ändringar. Det är alltid att föredra att lämna befintlig funktionalitet ensam, när man lägger till ny funktionalitet.
I OOP-terminologi kan du se det Växla_
har ett starkt beroende av Fläkt
. Det är här vårt problem ligger, och var vi borde göra våra förändringar.
gränssnitt Switchable funktion på (); funktion av (); klassen Fläktutrustningar Växlingsbar allmän funktion på () // kod för att starta fläkten offentlig funktion av () // kod för att stoppa fläkten klassen Switch_ privat $ switchable; funktion __construct (Switchable $ switchable) $ this-> switchable = $ switchable; funktion turnOn () $ this-> switchable-> på (); funktion turnOff () $ this-> switchable-> off ();
Denna lösning introducerar omkopplingsbar
gränssnitt. Det definierar de metoder som alla växlingsaktiverade objekt behöver implementera. De Fläkt
redskap omkopplingsbar
, och Växla_
accepterar en hänvisning till a omkopplingsbar
objekt inom sin konstruktör.
Hur hjälper det oss till?
För det första bryter denna lösning beroendet mellan Växla_
och Fläkt
. Växla_
har ingen aning om att det börjar bli en fan, och det bryr sig inte om det. För det andra introducerar a Ljus
klassen påverkar inte Växla_
eller omkopplingsbar
. Vill du styra a Ljus
objekt med din Växla_
klass? Skapa bara en Ljus
objekt och vidarebefordra det till Växla_
, så här:
klass Ljusanordningar Växlabar (allmän funktion på () // kod för att stänga av] allmän funktion av () // kod för att stänga av klass SomeWhereInYourCode function controlLight () $ light = new Light (); $ switch = ny switch _ ($ light); $ Kopplings-> TURNON (); $ Kopplings-> avstängning ();
LSP säger att en barnklass aldrig ska bryta moderklassens funktionalitet. Detta är extremt viktigt eftersom konsumenterna i en förälderklass förväntar sig att klassen ska agera på ett visst sätt. Att skicka en barnklass till en konsument bör bara fungera och inte påverka den ursprungliga funktionaliteten.
Detta är förvirrande vid första anblicken, så låt oss titta på ett annat klassiskt exempel:
klass rektangel privat $ bredd; privat $ höjd; funktion setWidth ($ bredd) $ this-> width = $ width; funktion setHeigth ($ heightth) $ this-> height = $ heightth; funktionsområde () returnera $ this-> width * $ this-> height;
Detta exempel definierar en enkel Rektangel
klass. Vi kan ställa in höjd och bredd, och dess område()
Metoden ger rektangelns område. Använda Rektangel
klassen kan se ut som följande:
klass geometri funktion rectArea (rektangel $ rektangel) $ rektangel-> setWidth (10); $ Rectangle-> setHeigth (5); returnera $ rektangel-> område ();
De rectArea ()
Metoden accepterar a Rektangel
objekt som argument, ställer in höjd och bredd och returnerar formens yta.
I skolan lär vi oss att fyrkanter är rektanglar. Detta antyder att om vi modellerar vårt program till vårt geometriska objekt, a Fyrkant
klassen bör förlänga a Rektangel
klass. Hur skulle en sådan klass se ut?
klass kvadrat sträcker sig rektangel // Vilken kod att skriva här?
Jag har svårt att ta reda på vad jag ska skriva i Fyrkant
klass. Vi har flera alternativ. Vi skulle kunna åsidosätta område()
metod och returnera torget av $ bredd
:
klass rektangel protected $ width; skyddad $ höjd; // ... // klass Square sträcker sig rektangel funktionsområde () returnera $ this-> width ^ 2;
Observera att jag ändrade Rektangel
s fält till skyddad
, ger Fyrkant
tillgång till dessa fält. Detta ser rimligt ur en geometrisk synvinkel. En fyrkant har lika sidor; återvänder torgets bredd är rimligt.
Vi har dock ett problem från en programmeringspunkt-of-view. Om Fyrkant
är en Rektangel
, Vi borde inte ha några problem med att mata in det Geometri
klass. Men genom att göra så kan du se det Geometri
Kodens kod ger ingen mening; den ställer in två olika värden för höjd och bredd. Det är därför en kvadrat är inte en rektangel i programmering. LSP kränkts.
Enhetstester ska springa fort - mycket snabbt.
Denna princip koncentrerar sig på att bryta stora gränssnitt till små, specialiserade gränssnitt. Grundtanken är att olika konsumenter i samma klass inte borde veta om de olika gränssnitten - bara de gränssnitt konsumenten behöver använda. Även om en konsument inte direkt använder alla offentliga metoder på ett objekt, beror det fortfarande på alla metoder. Så varför inte ge gränssnitt med det bara förklara de metoder som varje användare behöver?
Det är nära att gränssnitt ska tillhöra kunderna och inte till genomförandet. Om du skräddarsy dina gränssnitt till de konsumtiva klasserna kommer de att respektera ISP. Implementeringen i sig kan vara unik, eftersom en klass kan implementera flera gränssnitt.
Låt oss föreställa oss att vi genomför en börsapplikation. Vi har en mäklare som köper och säljer aktier, och det kan rapportera dess dagliga vinster och förluster. En mycket enkel implementering skulle innehålla något som a Mäklare
gränssnitt, a NYSEBroker
klass som implementerar Mäklare
och ett par användargränssnittskurser: en för att skapa transaktioner (TransactionsUI
) och en för rapportering (DailyReporter
). Koden för ett sådant system kan likna följande:
gränssnittsmäklare funktionsköp ($ symbol, $ volym); funktion sälja ($ symbol, $ volym); funktion dailyLoss ($ date); funktion dailyEarnings ($ date); klass NYSEBroker implementerar Broker public function buy ($ symbol, $ volym) // implementations går här offentlig funktion currentBalance () // implementations går här offentlig funktion dailyEarnings ($ date) // implementations går här public funktion dailyLoss ($ date) // implementations går här public function sell ($ symbol, $ volym) // implementations går här class TransactionsUI privat $ broker; funktion __construct (Broker $ broker) $ this-> broker = $ broker; funktion buyStocks () // UI logic här för att få information från en blankett till $ data $ this-> broker-> buy ($ data ['sybmol'], $ data ['volym']); funktion sellStocks () // UI logic här för att få information från en blankett till $ data $ this-> broker-> sälja ($ data ['sybmol'], $ data ['volume']); klass DailyReporter privat $ mäklare; funktion __construct (Broker $ broker) $ this-> broker = $ broker; funktion currentBalance () echo 'Aktuell balans för idag'. datum Tid()) . "\ N"; eko "Resultat:". $ this-> broker-> dailyEarnings (tid ()). "\ N"; eko "Förluster:". $ this-> broker-> dailyLoss (tid ()). "\ N";
Medan den här koden kan fungera bryter den till Internetleverantören. Både DailyReporter
och TransactionUI
beror på Mäklare
gränssnitt. Dock använder de bara en bråkdel av gränssnittet. TransactionUI
använder köpa()
och sälja()
metoder, medan DailyReporter
använder dailyEarnings ()
och dailyLoss ()
metoder.
Du kan argumentera för det
Mäklare
är inte sammanhängande eftersom det har metoder som inte är relaterade och därmed inte tillhör varandra.
Detta kan vara sant, men svaret beror på genomförandet av Mäklare
; Försäljning och köp kan vara starkt relaterad till nuvarande förluster och resultat. Till exempel får du inte tillåtas köpa aktier om du förlorar pengar.
Du kan också argumentera för det Mäklare
bryter också mot SRP. Eftersom vi har två klasser som använder den på olika sätt kan det finnas två olika användare. Jo, jag säger nej. Den enda användaren är förmodligen den faktiska mäklaren. Han / hon vill köpa, sälja och se deras nuvarande medel. Men återigen beror det faktiska svaret på hela systemet och verksamheten.
Internetleverantören bryts säkert. Bägge UI-klasserna beror på hela Mäklare
. Detta är ett vanligt problem, om du tror att gränssnitt tillhör deras implementeringar. Om du ändrar din synvinkel kan du föreslå följande design:
gränssnitt BrokerTransactions funktionsköp ($ symbol, $ volym); funktion sälja ($ symbol, $ volym); gränssnitt BrokerStatistics funktion dailyLoss ($ date); funktion dailyEarnings ($ date); klass NYSEBroker implementerar BrokerTransactions, BrokerStatistics public function buy $ symbol, $ volym // implementations går här offentlig funktion currentBalance () // implementations går här public function dailyEarnings ($ date) // implementations går här offentlig funktion dailyLoss ($ date) // implementations går här public function sell ($ symbol, $ volym) // implementations går här class TransactionsUI privat $ broker; funktion __construct (BrokerTransactions $ broker) $ this-> broker = $ broker; funktion buyStocks () // UI logic här för att få information från en blankett till $ data $ this-> broker-> buy ($ data ['sybmol'], $ data ['volym']); funktion sellStocks () // UI logic här för att få information från en blankett till $ data $ this-> broker-> sälja ($ data ['sybmol'], $ data ['volume']); klass DailyReporter privat $ mäklare; funktion __construct (BrokerStatistics $ broker) $ this-> broker = $ broker; funktion currentBalance () echo 'Aktuell balans för idag'. datum Tid()) . "\ N"; eko "Resultat:". $ this-> broker-> dailyEarnings (tid ()). "\ N"; eko "Förluster:". $ this-> broker-> dailyLoss (tid ()). "\ N";
Detta är faktiskt meningsfullt och respekterar Internetleverantören. DailyReporter
beror endast på BrokerStatistics
; det bryr sig inte och behöver inte veta om några försäljnings- och köpoperationer. TransactionsUI
, Å andra sidan vet bara om köp och försäljning. De NYSEBroker
är identisk med vår tidigare klass, förutom att den nu implementerar BrokerTransactions
och BrokerStatistics
gränssnitt.
Du kan tänka på Internetleverantören som en högre sammanhållningsprincip.
När båda gränssnittsklasserna berodde på Mäklare
gränssnittet liknade de två klasser, var och en med fyra fält, varav två användes i en metod och de andra två i en annan metod. Klassen skulle inte ha varit mycket sammanhängande.
Ett mer komplext exempel på denna princip finns i en av Robert C. Martins första dokument om ämnet: Gränssnittssegregationsprincipen.
Denna princip säger att högnivåmoduler inte bör bero på lågnivåmoduler. båda bör bero på abstraktioner. Abstraktioner bör inte bero på detaljer; detaljer bör bero på abstraktioner. Enkelt sagt, du borde vara beroende av abstraktioner så mycket som möjligt och aldrig på konkreta implementeringar.
Tricket med DIP är att du vill vända beroende, men vill alltid hålla kontrollflödet. Låt oss granska vårt exempel från OCP (the Växla
och Ljus
klasser). I den ursprungliga implementeringen hade vi en strömbrytare som direkt kontrollerade ett ljus.
Som du kan se, beror både beroendet och kontrollen från Växla
mot Ljus
. Medan det här är vad vi vill, vill vi inte direkt bero på Ljus
. Så vi introducerade ett gränssnitt.
Det är fantastiskt hur enkelt att införa ett gränssnitt gör att vår kod respekterar både DIP och OCP. Som ni kan se nej, beror klassen på det konkreta genomförandet av Ljus
, och båda Ljus
och Växla
beror på omkopplingsbar
gränssnitt. Vi inverterade beroendet, och kontrollflödet var oförändrat.
En annan viktig aspekt av din kod är din design på hög nivå och allmän arkitektur. En entangled arkitektur producerar kod som är svår att modifiera. Att hålla en ren arkitektur är avgörande, och det första steget är att förstå hur man skiljer din kods olika problem.
I den här bilden försökte jag sammanfatta de viktigaste problemen. I centrum av schemat är vår affärslogik. Det ska vara väl isolerat från resten av världen och kunna arbeta och uppträda som förväntat utan att någon av de andra delarna finns. Se den som ortogonalitet på en högre nivå.
Från höger har du din "huvud" - ingångspunkten till applikationen - och de fabriker som skapar objekt. En idealisk lösning skulle få sina föremål från specialiserade fabriker, men det är oftast omöjligt eller opraktiskt. Fortfarande bör du använda fabriker när du har möjlighet att göra det och behålla dem utanför din affärslogik.
Sedan, längst ner (i orange) har vi uthållighet (databaser, filåtkomst, nätverkskommunikation) i syfte att beständig information. Inget objekt i vår affärslogik borde veta hur uthållighet fungerar.
Till vänster är leveransmekanismen.
En MVC, som Laravel eller CakePHP, borde bara vara leveransmekanismen, inget mer.
Detta låter dig byta en mekanism med en annan utan att röra din affärslogik. Det här kan vara skandalöst för några av er. Vi får veta att vår affärslogik ska placeras i våra modeller. Jo, jag håller inte med mig. Våra modeller ska vara "request models", dvs dumma dataobjekt som används för att överföra information från MVC till affärslogiken. Eventuellt ser jag inga problem inklusive inmatningsvalidering i modellerna, men inget mer. Affärslogik bör inte finnas i modellerna.
När du tittar på programmets arkitektur eller katalogstruktur bör du se en struktur som föreslår vad programmet gör i motsats till vilken ram eller databas du använde.
Slutligen, se till att alla beroenden pekar mot vår affärslogik. Användargränssnitt, fabriker, databaser är mycket konkreta implementeringar, och du borde aldrig bero på dem. Omvända beroenden för att peka mot vår affärslogik modularizes vårt system, så att vi kan ändra beroenden utan att ändra affärslogiken.
Designmönster spelar en viktig roll för att göra kod enklare att modifiera genom att erbjuda en gemensam designlösning som alla programmerare kan förstå. Ur en strukturell synvinkel är designmönster naturligtvis fördelaktiga. De är väl testade och genomtänkta lösningar.
Om du vill lära dig mer om designmönster skapade jag en Tuts + Premium kurs på dem!
Testdriven utveckling uppmuntrar skrivkod som är lätt att testa. TDD tvingar dig att respektera de flesta ovanstående principer för att göra din kod lätt att testa. Injicera beroenden och skriva ortogonala klasser är väsentliga; annars slutar du med stora testmetoder. Enhetstesterna bör springa fort - mycket snabbt, faktiskt, och allt som inte testas ska vara bespottat. Mocking många komplexa klasser för ett enkelt test kan vara överväldigande. Så när du upptäcker att du tappar tio objekt för att testa en enda metod i en klass kan du ha problem med din kod ... inte ditt test.
I slutet av dagen kommer allt ner till hur mycket du bryr dig om din källkod. Att ha teknisk kunskap räcker inte. du måste använda den kunskapen om och om igen, aldrig vara 100% nöjd med din kod. Du måste vilja göra din kod lätt att underhålla, rena och öppna för förändring.
Tack för att du läser och känner dig fri att bidra med dina tekniker i kommentarerna nedan.