Vier regels voor eenvoudiger iOS-softwareontwerp

In de late jaren 1990, tijdens het ontwikkelen van Extreme Programming, bedacht de beroemde softwareontwikkelaar Kent Beck een lijst met regels voor eenvoudig softwareontwerp.

Volgens Kent Beck, een goed softwareontwerp:

  • Voert alle tests uit
  • Bevat geen duplicatie
  • Drukt de bedoeling van de programmeur uit
  • Minimaliseert het aantal klassen en methoden

In dit artikel zullen we bespreken hoe deze regels kunnen worden toegepast op de iOS-ontwikkelingswereld door praktische iOS-voorbeelden te geven en te bespreken hoe we ervan kunnen profiteren.

Voert alle tests uit

Softwareontwerp helpt ons een systeem te maken dat werkt zoals bedoeld. Maar hoe kunnen we verifiëren dat een systeem zal werken zoals aanvankelijk bedoeld door zijn ontwerp? Het antwoord is door tests te maken die het valideren.

Helaas worden tests in het iOS-universum meestal vermeden ... Maar om een ​​goed ontworpen software te maken, moeten we altijd Swift-code schrijven met het oog op testbaarheid.

Laten we twee principes bespreken die het schrijven van tests en systeemontwerp eenvoudiger kunnen maken. En ze zijn Single Responsibility Principle en Dependency Injection.

Single Responsibility Principle (SRP)

SRP stelt dat een klasse één en slechts één reden moet hebben om te veranderen. De SRP is een van de eenvoudigste principes en een van de moeilijkste om goed te krijgen. Het combineren van verantwoordelijkheden is iets dat we van nature doen.

Laten we een voorbeeld geven van een code die het echt moeilijk is om te testen en daarna opnieuw bepalen met behulp van SRP. Bespreek vervolgens hoe het de code testbaar maakte.

Stel dat we momenteel een PaymentViewController van onze huidige viewcontroller moeten presenteren, PaymentViewController zou zijn view moeten configureren met afhankelijk van de prijs van ons betaalproduct. In ons geval is de prijs variabel, afhankelijk van sommige externe gebruikersgebeurtenissen.

De code voor deze implementatie ziet er momenteel als volgt uit:

Hoe kunnen we deze code testen? Wat moeten we eerst testen? Is de prijskorting correct berekend? Hoe kunnen we de betalingsgebeurtenissen bespotten om de korting te testen?

Het schrijven van tests voor deze klas zou ingewikkeld zijn, we zouden een betere manier moeten vinden om het te schrijven. Laten we eerst het grote probleem aanpakken. We moeten onze afhankelijkheden ontwarren.

We zien dat we logica hebben voor het laden van ons product. We hebben betalingsgebeurtenissen waardoor de gebruiker in aanmerking komt voor korting. We hebben kortingen, een kortingsberekening en de lijst gaat maar door.

Laten we proberen deze eenvoudig te vertalen in Swift-code.

We hebben een PaymentManager gemaakt die onze logica met betrekking tot betalingen beheert, en een aparte PriceCalculator die gemakkelijk kan worden getest. Ook een data-loader die verantwoordelijk is voor de netwerk- of database-interactie voor het laden van onze producten.

We hebben ook vermeld dat we een klasse nodig hebben die verantwoordelijk is voor het beheer van de kortingen. Laten we het CouponManager noemen en laat het ook de kortingsbonnen van gebruikers beheren.

Onze betalingsweergavecontroller kan er dan als volgt uitzien:

We kunnen nu tests schrijven zoals

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

en vele andere! Door nu afzonderlijke objecten te maken, voorkomen we onnodige duplicatie en maken we ook een code waarvoor u gemakkelijk tests kunt schrijven.

Afhankelijkheid injectie

Het tweede principe is Dependency Injection. En we zagen uit de bovenstaande voorbeelden dat we de afhankelijkheidsinjectie al gebruikten bij onze objectinitializers.

Er zijn twee grote voordelen van het injecteren van onze afhankelijkheden zoals hierboven. Het maakt duidelijk op welke afhankelijkheden onze typen vertrouwen en het stelt ons in staat om onechte objecten in te voegen wanneer we willen testen in plaats van de echte.

Een goede techniek is om protocollen voor onze objecten te maken en een concrete implementatie te bieden door het echte en het nep-object, zoals de volgende:

Nu kunnen we eenvoudig beslissen welke klasse we als afhankelijkheid willen injecteren.

Strakke koppeling maakt het moeilijk om tests te schrijven. Dus, hoe meer tests we ook schrijven, hoe meer we principes gebruiken zoals DIP en tools zoals afhankelijkheidsinjectie, interfaces en abstractie om de koppeling te minimaliseren.

Door de code testbaarder te maken, elimineert u niet alleen onze angst om hem te breken (omdat we de test zullen schrijven die ons zal ondersteunen), maar draagt ​​hij ook bij aan het schrijven van schonere code.

Dit deel van het artikel ging meer over het schrijven van code die testbaar is dan over het schrijven van de eigenlijke eenheids-test. Als je meer wilt weten over het schrijven van de eenheidstest, kun je dit artikel bekijken waarin ik het spel van het leven creëer met behulp van testgestuurde ontwikkeling.

Bevat geen duplicatie

Duplicatie is de primaire vijand van een goed ontworpen systeem. Het vertegenwoordigt extra werk, extra risico, voegt onnodige complexiteit toe.

In dit gedeelte zullen we bespreken hoe we het sjabloonontwerppatroon kunnen gebruiken voor het verwijderen van veel voorkomende duplicaties in iOS. Om het beter te begrijpen gaan we de implementatie van een real-life chat refacteren.

Stel dat we momenteel in onze app een standaard chatsectie hebben. Er komt een nieuwe vereiste en nu willen we een nieuw type chat implementeren - een livechat. Een chat die berichten met maximaal 20 tekens mag bevatten en deze chat verdwijnt wanneer we de chatweergave sluiten.

Deze chat heeft dezelfde weergaven als onze huidige chat, maar heeft een paar verschillende regels:

  1. Netwerkverzoek voor het verzenden van chatberichten zal anders zijn.

2. Chatberichten moeten kort zijn, maximaal 20 tekens voor een bericht.

3. Chatberichten mogen niet in onze lokale database worden bewaard.

Stel dat we MVP-architectuur gebruiken en momenteel omgaan met de logica voor het verzenden van chatberichten in onze presentator. Laten we proberen nieuwe regels toe te voegen voor ons nieuwe chattype met de naam live-chat.

Een naïeve implementatie zou er als volgt uitzien:

Maar wat gebeurt er als we in de toekomst veel meer chattypen hebben?
Als we doorgaan met het toevoegen van iets anders dat de status van onze chat in elke functie controleert, wordt de code moeilijk te lezen en te onderhouden. Het is ook nauwelijks te testen en statuscontrole zou overal in de presentator worden gedupliceerd.

Dit is waar het sjabloonpatroon in gebruik komt. Het sjabloonpatroon wordt gebruikt wanneer we meerdere implementaties van een algoritme nodig hebben. De sjabloon wordt gedefinieerd en vervolgens verder gebouwd met verdere variaties. Gebruik deze methode wanneer de meeste subklassen hetzelfde gedrag moeten implementeren.

We kunnen een protocol maken voor Chat Presenter en we scheiden methoden die anders worden geïmplementeerd door concrete objecten in Chat Presenter Phases.

We kunnen onze presentator nu conformeren aan het IChatPresenter

Onze Presenter zorgt nu voor het verzenden van berichten door algemene functies in zichzelf aan te roepen en delegeert de functies die anders kunnen worden geïmplementeerd.

Nu kunnen we maken Create objecten die voldoen aan de presentator fasen gebaseerd en configureren van deze functies op basis van hun behoeften.

Als we afhankelijkheidsinjectie in onze view-controller gebruiken, kunnen we dezelfde view-controller nu in twee verschillende gevallen hergebruiken.

Door gebruik te maken van ontwerppatronen kunnen we onze iOS-code echt vereenvoudigen. Als u daar meer over wilt weten, geeft het volgende artikel een nadere toelichting.

Expressief

Het merendeel van de kosten van een softwareproject bestaat uit langdurig onderhoud. Eenvoudig te lezen en te onderhouden code schrijven is een must voor softwareontwikkelaars.

We kunnen meer expressieve code aanbieden door goede Naming, SRP en Writing-test te gebruiken.

Naming

Nummer één ding dat de code expressiever maakt - en het is naamgeving. Het is belangrijk om namen te schrijven die:

  • Onthul intentie
  • Vermijd desinformatie
  • Zijn gemakkelijk doorzoekbaar

Als het gaat om het benoemen van klassen en functies, is het een goede truc om een ​​zelfstandig naamwoord of een zelfstandig naamwoord-zin te gebruiken voor klassen en werkwoorden of werkwoord-namen voor methoden.

Ook bij het gebruik van verschillende ontwerppatronen is het soms goed om de patroonnamen zoals Command of Visitor in de klassennaam toe te voegen. Dus de lezer zou meteen weten welk patroon daar wordt gebruikt zonder dat hij alle code hoeft te lezen om daarachter te komen.

SRP gebruiken

Een ander ding dat code expressief maakt, is het gebruik van het Single Responsibility Principle dat hierboven werd vermeld. Je kunt jezelf uitdrukken door je functies en klassen klein en voor een enkel doel te houden. Kleine klassen en functies zijn meestal gemakkelijk te benoemen, gemakkelijk te schrijven en gemakkelijk te begrijpen. Een functie moet slechts voor één doel dienen.

Schrijftest

Schrijftests bieden ook veel duidelijkheid, vooral bij het werken met oudere code. Goed geschreven eenheidstests zijn ook expressief. Een primair doel van tests is om als voorbeeld te dienen als documentatie. Iemand die onze tests leest, moet snel kunnen begrijpen waar een les over gaat.

Minimaliseer het aantal klassen en methoden

De functies van een klasse moeten kort blijven, een functie moet altijd maar één ding uitvoeren. Als een functie te veel regels heeft, kan dat het geval zijn dat er acties worden uitgevoerd die kunnen worden gescheiden in twee of meer afzonderlijke functies.

Een goede aanpak is om fysieke lijnen te tellen en te proberen te streven naar maximaal vier tot zes regels met functies, in de meeste gevallen alles dat meer gaat dan dat aantal lijnen dat moeilijk te lezen en te onderhouden kan zijn.

Een goed idee in iOS is om de configuratieaanroepen te hakken die we meestal doen met de functies viewDidLoad of viewDidAppear.

Op deze manier zou elk van de functies klein en onderhoudbaar zijn in plaats van één puinhoop viewDidLoad-functie. Hetzelfde moet ook gelden voor app-gemachtigde. We moeten voorkomen dat elke configuratie ondidFinishLaunchingWithOptions-methode en afzonderlijke configuratiefuncties of zelfs betere configuratieklassen wordt weggegooid.

Met functies is het een beetje eenvoudiger om te meten of we het lang of kort houden, we kunnen meestal alleen vertrouwen op het tellen van de fysieke lijnen. Bij klassen gebruiken we een andere maat. We tellen verantwoordelijkheden. Als een klasse slechts vijf methoden heeft, betekent dit niet dat de klasse klein is, kan het zijn dat het teveel verantwoordelijkheden heeft met alleen die methoden.

Een bekend probleem in iOS is de grote omvang van UIViewControllers. Het is waar dat het door Apple View Controller-ontwerp moeilijk is om deze objecten voor één doel te houden, maar we moeten ons best doen.

Er zijn veel manieren om UIViewControllers klein te maken. Mijn voorkeur gaat uit naar een architectuur met een betere scheiding van problemen, zoals VIPER of MVP, maar dat betekent niet dat we het ook niet beter kunnen maken in Apple MVC.

Door zoveel mogelijk problemen te scheiden, kunnen we met elke architectuur een behoorlijk behoorlijke code bereiken. Het idee is om single-purpose klassen te maken die kunnen dienen als helpers voor de viewcontrollers en de code leesbaarder en testbaarder maken.

Sommige dingen die eenvoudig kunnen worden vermeden zonder excuus in viewcontrollers zijn:

  • In plaats van netwerkcode rechtstreeks te schrijven, moet er een NetworkManager een klasse zijn die verantwoordelijk is voor netwerkoproepen
  • In plaats van gegevens in viewcontrollers te manipuleren, kunnen we eenvoudig een DataManager maken, een klasse die daarvoor verantwoordelijk is.
  • In plaats van met UserDefaults-reeksen in UIViewController te spelen, kunnen we daar een gevel voor maken.

Ten slotte

Ik geloof dat we software moeten samenstellen uit componenten die nauwkeurig zijn benoemd, eenvoudig, klein, verantwoordelijk voor één ding en herbruikbaar.

In dit artikel hebben we vier regels voor eenvoudig ontwerp door Kent Beck besproken en praktische voorbeelden gegeven van hoe we ze kunnen implementeren in een iOS-ontwikkelomgeving.

Als je dit artikel leuk vond, moet je klappen om je steun te tonen. Volg mij om nog veel meer artikelen te bekijken die je iOS Developer-vaardigheden naar een volgend niveau kunnen brengen.

Als je vragen of opmerkingen hebt, kun je hier een bericht achterlaten of een e-mail sturen naar arlindaliu.dev@gmail.com.