Fire regler for enklere iOS-softwaredesign

I slutningen af ​​1990'erne, mens han udviklede Extreme Programming, kom den berømte softwareudvikler Kent Beck med en liste over regler for simpelt softwaredesign.

Ifølge Kent Beck er et godt softwaredesign:

  • Kører alle testene
  • Indeholder ingen kopiering
  • Udtrykker programmets intention
  • Minimerer antallet af klasser og metoder

I denne artikel diskuterer vi, hvordan disse regler kan anvendes på iOS-udviklingsverdenen ved at give praktiske iOS-eksempler og diskutere, hvordan vi kan drage fordel af dem.

Kører alle testene

Software design hjælper os med at oprette et system, der fungerer som tilsigtet. Men hvordan kan vi verificere, at et system vil fungere som oprindeligt tilsigtet med dets design? Svaret er ved at oprette test, der validerer det.

Desværre i iOS-udviklingsunivers undgås de fleste af de tider ... Men for at skabe en godt designet software, skal vi altid skrive Swift-kode med testbarhed i tankerne.

Lad os diskutere to principper, der kan gøre testskrivning og systemdesign enklere. Og de er et enkelt ansvarsprincip og injektion til afhængighed.

Enkeltansvarsprincip (SRP)

SRP siger, at en klasse skal have en og kun en grund til at ændre sig. SRP er et af de enkleste principper og en af ​​de sværeste at få ret. At blande ansvar er noget, vi gør naturligt.

Lad os give et eksempel på nogle kode, som det er virkelig svært at teste, og efter det refaktor det ved hjælp af SRP. Diskuter derefter, hvordan den gjorde koden testbar.

Antag, at vi i øjeblikket har brug for at præsentere en PaymentViewController fra vores nuværende visningskontroller, skal PaymentViewController konfigurere sin visning med afhængigt af vores pris for betalingsprodukt. I vores tilfælde er prisen variabel afhængig af nogle eksterne brugerhændelser.

Koden til denne implementering ser i øjeblikket således ud:

Hvordan kan vi teste denne kode? Hvad skal vi teste først? Beregnes prisrabatningen korrekt? Hvordan kan vi bespotte betalingshændelserne for at teste rabatten?

At skrive prøver til denne klasse ville være kompliceret, vi skulle finde en bedre måde at skrive det på. Nå, lad os først tackle det store problem. Vi er nødt til at løsne vores afhængigheder.

Vi ser, at vi har logik for indlæsning af vores produkt. Vi har betalingsbegivenheder, der gør brugeren berettiget til en rabat. Vi har rabatter, en rabatberegning, og listen fortsætter.

Så lad os prøve at oversætte disse til Swift-kode.

Vi oprettede en PaymentManager, der administrerer vores logik relateret til betalinger, og Separer PriceCalculator, som den let kan testes. Også en datalaster, der er ansvarlig for netværk eller database interaktion for indlæsning af vores produkter.

Vi nævnte også, at vi har brug for en klasse, der er ansvarlig for at styre rabatterne. Lad os kalde det CouponManager, og lad det også administrere brugerrabatkuponer.

Vores betalingsvisningskontroller kan derefter se ud som følgende:

Vi kan nu skrive tests som

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

og mange andre! Ved at oprette separate objekter nu undgår vi unødvendig duplikering og oprettede også en kode, som det er let at skrive test til.

Afhængighed Injektion

Det andet princip er afhængighedsinjektion. Og fra de ovenstående eksempler så vi, at vi allerede brugte afhængighedsinjektion på vores objektinitialisatorer.

Der er to store fordele ved at injicere vores afhængigheder som ovenfor. Det gør det klart, hvilke afhængigheder vores typer er afhængige af, og det giver os mulighed for at indsætte uekte objekter, når vi vil teste i stedet for de rigtige.

En god teknik er at oprette protokoller til vores objekter og give konkret implementering af det virkelige og det spotte objekt som følgende:

Nu kan vi nemt beslutte, hvilken klasse vi vil injicere som en afhængighed.

Stram kobling gør det vanskeligt at skrive prøver. Så på lignende måde, jo flere test vi skriver, jo mere bruger vi principper som DIP og værktøjer som afhængighedsinjektion, grænseflader og abstraktion for at minimere koblingen.

At gøre koden mere testbar eliminerer ikke kun vores frygt for at bryde den (da vi vil skrive den test, der vil sikkerhedskopiere os), men bidrager også til at skrive renere kode.

Denne del af artiklen handlede mere om, hvordan man skriver kode, der kan testes, end at skrive den faktiske enhedstest. Hvis du vil lære mere om at skrive enhedstesten, kan du tjekke denne artikel, hvor jeg opretter livets spil ved hjælp af testdrevet udvikling.

Indeholder ingen kopiering

Duplisering er den primære fjende i et veludviklet system. Det repræsenterer ekstra arbejde, yderligere risiko, tilføjer unødvendig kompleksitet.

I dette afsnit diskuterer vi, hvordan vi kan bruge skabelondesignmønsteret til at fjerne almindelig duplikering i iOS. For at gøre det lettere at forstå, vil vi refaktor implementere en real-chat.

Antag, at vi i vores app i øjeblikket har et standardchatsektion. Et nyt krav kommer op, og nu vil vi implementere en ny type chat - en live-chat. En chat, der skal indeholde beskeder med maksimalt 20 antal tegn, og denne chat forsvinder, når vi afviser chatvisningen.

Denne chat har de samme visninger som vores nuværende chat, men har nogle få forskellige regler:

  1. Netværksanmodning om at sende chatbeskeder vil være anderledes.

2. Chatbeskeder skal være korte, højst 20 tegn for meddelelse.

3. Chatmeddelelser bør ikke vedholdes i vores lokale database.

Antag, at vi bruger MVP-arkitektur, og at vi i øjeblikket håndterer logikken for at sende chatbeskeder i vores programleder. Lad os prøve at tilføje nye regler for vores nye chat-type, der hedder live-chat.

En naiv implementering ville være som følger:

Men hvad sker der, hvis vi i fremtiden vil have meget flere chattyper?
Hvis vi fortsætter med at tilføje, hvis ellers kontrollerer tilstanden for vores chat i hver funktion, vil koden blive rodet svært at læse og vedligeholde. Det er også næppe testbart, og tilstandskontrol vil blive duplikeret over hele programlederens omfang.

Det er her skabelonmønsteret bruges. Skabelonmønsteret bruges, når vi har brug for flere implementeringer af en algoritme. Skabelonen defineres og bygger derefter på med yderligere variationer. Brug denne metode, når de fleste underklasser har brug for at implementere den samme opførsel.

Vi kan oprette en protokol til Chat Presentator og vi adskiller metoder, der vil blive implementeret forskelligt af konkrete objekter i Chat Presentationsfaser.

Vi kan nu få vores programleder til at overholde IChatPresenter

Vores præsentant håndterer nu meddelelsessendelsen ved at ringe til fælles funktioner i sig selv og delegerer de funktioner, der kan implementeres forskelligt.

Nu kan vi tilbyde Opret objekter, der er i overensstemmelse med præsentationsfaserne baseret og konfigurere disse funktioner baseret på deres behov.

Hvis vi bruger afhængighedsinjektion i vores view-controller, kan vi nu genbruge den samme view-controller i to forskellige tilfælde.

Ved at bruge designmønstre kan vi virkelig forenkle vores iOS-kode. Hvis du vil vide mere om det, giver følgende artikel yderligere forklaring.

udtryksfuld

Størstedelen af ​​prisen på et softwareprojekt er ved langvarig vedligeholdelse. Det er et must for softwareudviklere at skrive let at læse og vedligeholde kode.

Vi kan tilbyde mere ekspressiv kode ved at bruge god navngivning, anvendelse af SRP og skrivningstest.

navngivning

Nummer én ting, der gør koden mere udtryksfuld - og den er navngivning. Det er vigtigt at skrive navne, der:

  • Vis hensigt
  • Undgå desinformation
  • Kan let søges

Når det kommer til navngivning af klasser og funktioner, er et godt trick at bruge et substantiv eller substantiv-sætning til klasser og brugerverb eller verbfrase-navne til metoder.

Også når man bruger forskellige designmønstre er det sommetider godt at tilføje mønsternavne som kommando eller besøgende i klassens navn. Så læseren vil straks vide, hvilket mønster der bruges der uden at have behov for at læse hele koden for at finde ud af det.

Brug af SRP

En anden ting, der gør kodeudtryksfuld, er at bruge Single Responsibility Principle, der blev nævnt ovenfra. Du kan udtrykke dig selv ved at holde dine funktioner og klasser små og til et enkelt formål. Små klasser og funktioner er normalt lette at navngive, lette at skrive og lette at forstå. En funktion skal kun tjene til et formål.

Skrivetest

Skrivningstest bringer også masser af klarhed, især når man arbejder med legacy code. Velskrevne enhedsprøver er også udtryksfulde. Et primært mål med prøver er at fungere som dokumentation ved eksempel. En person, der læser vores prøver, skal kunne få en hurtig forståelse af, hvad en klasse handler om.

Minimer antallet af klasser og metoder

Funktionerne i en klasse skal forblive korte, en funktion skal altid kun udføre en ting. Hvis en funktion har for mange linjer, der kan være tilfældet, at den udfører handlinger, der kan opdeles i to eller flere separate funktioner.

En god tilgang er at tælle fysiske linjer og forsøge at sigte mod maksimalt fire til seks linjer med funktioner, i de fleste tilfælde alt, hvad der går mere end det antal linjer, det kan blive svært at læse og vedligeholde.

En god idé i iOS er at hugge de konfigurationsopkald, som vi normalt gør på viewDidLoad eller viewDidAppear-funktioner.

På denne måde ville hver af funktionerne være små og vedligeholdelige i stedet for en mess viewDidLoad-funktion. Det samme bør også gælde for app-delegeret. Vi bør undgå at kaste enhver konfiguration ondidFinishLaunchingWithOptions-metoden og separate konfigurationsfunktioner eller endnu bedre konfigurationsklasser.

Med funktioner er det lidt lettere at måle, om vi holder det langt eller kort, vi kan de fleste af de gange bare stole på at tælle de fysiske linjer. Med klasser bruger vi en anden måling. Vi tæller ansvar. Hvis en klasse kun har fem metoder, betyder det ikke, at klassen er lille, det kan være, at den har for mange opgaver med kun disse metoder.

Et kendt problem i iOS er den store størrelse af UIViewControllers. Det er rigtigt, at det med apple view controller-design er svært at holde disse objekter til at tjene et enkelt formål, men vi bør gøre vores bedste.

Der er mange måder at gøre UIViewControllers små på. Jeg foretrækker at bruge en arkitektur, der har bedre adskillelse af bekymringer noget som VIPER eller MVP, men det betyder ikke, at vi ikke kan gøre det bedre i apple MVC også.

Ved at forsøge at adskille så mange bekymringer kan vi nå temmelig anstændigt kode med enhver arkitektur. Ideen er at oprette klasser til én formål, der kan tjene som hjælpere til visningskontrollerne og gøre koden mere læselig og testbar.

Nogle ting, der simpelthen kan undgås uden undskyldning i synskontrollere er:

  • I stedet for at skrive netværkskode direkte skal der være en NetworkManager, en klasse, der er ansvarlig for netværksopkald
  • I stedet for at manipulere data i visningskontrollere kan vi blot oprette en DataManager en klasse, der er ansvarlig for det.
  • I stedet for at lege med UserDefaults strenge i UIViewController kan vi skabe en facade over det.

Afslutningsvis

Jeg mener, at vi skal komponere software fra komponenter, der er nøjagtigt navngivne, enkle, små, ansvarlige for én ting og genanvendelige.

I denne artikel diskuterede vi fire regler for enkel design af Kent Beck og gav praktiske eksempler på, hvordan vi kan implementere dem i iOS-udviklingsmiljø.

Hvis du nød denne artikel, skal du sørge for at klappe for at vise din støtte. Følg mig for at se mange flere artikler, der kan tage dine iOS-udviklerværdigheder til et næste niveau.

Hvis du har spørgsmål eller kommentarer, er du velkommen til at lægge en note her eller maile mig til arlindaliu.dev@gmail.com.