Fire regler for enklere design av iOS-programvare

På slutten av 1990-tallet, mens han utviklet Extreme Programming, kom den kjente programvareutvikleren Kent Beck med en liste over regler for enkel programvaredesign.

I følge Kent Beck, en god programvaredesign:

  • Kjører alle testene
  • Inneholder ingen duplisering
  • Uttrykker intensjonen til programmereren
  • Minimerer antall klasser og metoder

I denne artikkelen vil vi diskutere hvordan disse reglene kan brukes på iOS-utviklingsverdenen ved å gi praktiske iOS-eksempler og diskutere hvordan vi kan dra nytte av dem.

Kjører alle testene

Programvaredesign hjelper oss med å lage et system som fungerer som det er ment. Men hvordan kan vi bekrefte at et system vil fungere slik det først ble ment med utformingen? Svaret er ved å lage tester som validerer det.

Dessverre unngås i iOS-utviklingsunivers testene de fleste ganger ... Men for å lage en godt designet programvare, bør vi alltid skrive Swift-kode med testbarhet i tankene.

La oss diskutere to prinsipper som kan gjøre testskriving og systemdesign enklere. Og de er enkeltansvarsprinsipp og injeksjon avhengighet.

Enkeltansvarsprinsipp (SRP)

SRP uttaler at en klasse skal ha en, og bare en grunn til å endre. SRP er et av de enkleste prinsippene, og en av de vanskeligste å få rett. Å blande ansvar er noe vi gjør naturlig.

La oss gi et eksempel på noen kode som det er veldig vanskelig å teste, og etter det refaktorere det ved å bruke SRP. Diskuter deretter hvordan den gjorde koden testbar.

Anta at vi for øyeblikket må presentere en PaymentViewController fra vår nåværende visningskontroller, bør PaymentViewController konfigurere visningen sin avhengig av vår pris for betalingsprodukt. I vårt tilfelle er prisen variabel avhengig av noen eksterne brukerhendelser.

Koden for denne implementeringen ser for øyeblikket ut slik:

Hvordan kan vi teste denne koden? Hva skal vi teste først? Beregnes prisrabatten riktig? Hvordan kan vi spotte betalingshendelser for å teste rabatten?

Å skrive tester for denne klassen ville være komplisert, vi burde finne en bedre måte å skrive den på. La oss først ta det store problemet. Vi må løsrive avhengighetene våre.

Vi ser at vi har logikk for å laste inn produktet vårt. Vi har betalingshendelser som gjør brukeren kvalifisert for rabatt. Vi har rabatter, en rabattberegning og listen fortsetter.

Så la oss prøve å bare oversette disse til Swift-kode.

Vi opprettet en PaymentManager som administrerer logikken vår relatert til betalinger, og Separate PriceCalculator som den enkelt kan testes. En datalaster som er ansvarlig for nettverk eller database interaksjon for lasting av produktene våre.

Vi nevnte også at vi trenger en klasse som er ansvarlig for å håndtere rabattene. La oss kalle det CouponManager og la det også administrere brukerrabattkuponger.

Betalingsvisningskontrolleren vår kan da se slik ut:

Vi kan skrive tester nå

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

og mange andre! Ved å opprette separate objekter nå unngår vi unødvendig duplisering og opprettet også en kode som det er lett å skrive tester for.

Avhengighetsinjeksjon

Det andre prinsippet er avhengighetsinjeksjon. Og vi så fra eksemplene over at vi allerede brukte avhengighetsinjeksjon på objektinitialisererne våre.

Det er to store fordeler med å injisere avhengighetene våre som ovenfor. Det gjør det klart hvilke avhengigheter typene våre er avhengige av, og det gjør at vi kan sette inn spotte objekter når vi vil teste i stedet for de virkelige.

En god teknikk er å lage protokoller for objektene våre og gi konkret implementering av det virkelige og det spotte objektet som følgende:

Nå kan vi enkelt bestemme hvilken klasse vi vil injisere som en avhengighet.

Tett kobling gjør det vanskelig å skrive tester. Så på samme måte, jo flere tester vi skriver, jo mer bruker vi prinsipper som DIP og verktøy som avhengighetsinjeksjon, grensesnitt og abstraksjon for å minimere koblingen.

Å gjøre koden mer testbar eliminerer ikke bare frykten vår for å bryte den (siden vi vil skrive testen som vil sikkerhetskopiere oss), men bidrar også til å skrive renere kode.

Denne delen av artikkelen var mer opptatt av hvordan du kan skrive kode som kan testes enn å skrive den faktiske enhetstesten. Hvis du vil lære mer om å skrive enhetstesten, kan du sjekke ut denne artikkelen der jeg lager livsspillet ved hjelp av testdrevet utvikling.

Inneholder ingen duplisering

Duplisering er den primære fienden til et godt designet system. Det representerer merarbeid, tilleggsrisiko, tilfører unødvendig kompleksitet.

I denne delen diskuterer vi hvordan vi kan bruke maldesignmønsteret for å fjerne vanlig duplisering i iOS. For å gjøre det enklere å forstå, skal vi refactor implementere en ekte chat.

Anta at vi for øyeblikket har en standard chat-seksjon i appen vår. Et nytt krav dukker opp, og nå ønsker vi å implementere en ny type chat - en live-chat. En chat som skal inneholde meldinger med maksimalt 20 antall tegn, og denne chatten vil forsvinne når vi avviser chatvisningen.

Denne chatten har de samme visningene som vår nåværende chat, men vil ha noen få forskjellige regler:

  1. Nettverksforespørsel om sending av chatmeldinger vil være annerledes.

2. Chat-meldinger må være korte, ikke mer enn 20 tegn for melding.

3. Chat-meldinger skal ikke fortsette i vår lokale database.

Anta at vi bruker MVP-arkitektur og at vi for øyeblikket håndterer logikken for å sende chatmeldinger i programlederen vår. La oss prøve å legge til nye regler for den nye chat-typen vår som heter live-chat.

En naiv implementering vil være som følgende:

Men hva skjer hvis vi i fremtiden kommer til å ha mye flere chat-typer?
Hvis vi fortsetter å legge til hvis annet som sjekker tilstanden til chatten vår i hver funksjon, vil koden bli rotete vanskelig å lese og vedlikeholde. Det er dessuten knapt testbar, og tilstandskontroll vil bli duplisert over programlederens omfang.

Det er her malmønsteret kommer i bruk. Malmønsteret brukes når vi trenger flere implementeringer av en algoritme. Malen blir definert og deretter bygd videre med ytterligere varianter. Bruk denne metoden når de fleste underklasser trenger å implementere den samme oppførselen.

Vi kan lage en protokoll for Chat Presentator og vi skiller metoder som vil bli implementert annerledes av konkrete objekter i Chat Presentator Faser.

Vi kan nå gjøre programlederen vår i samsvar med IChatPresenter

Presentatøren vår håndterer nå meldingen som sendes ved å ringe vanlige funksjoner i seg selv og delegerer funksjonene som kan implementeres annerledes.

Nå kan vi tilby Lag objekter som samsvarer med programlederfasene basert og konfigurerer disse funksjonene basert på deres behov.

Hvis vi bruker avhengighetsinjeksjon i vår visningskontroller, kan vi nå gjenbruke den samme visningskontrolleren i to forskjellige tilfeller.

Ved å bruke designmønstre kan vi virkelig forenkle iOS-koden vår. Hvis du vil vite mer om det, gir følgende artikkel ytterligere forklaring.

uttrykks

Størstedelen av kostnadene for et programvareprosjekt er vedlikeholdt på lang sikt. Å skrive lettlest og vedlikeholde kode er et must for programvareutviklere.

Vi kan tilby mer ekspressiv kode ved å bruke god navngivning, bruk av SRP og skrivetest.

Naming

Nummer én ting som gjør koden mer uttrykksfull - og den er navngiving. Det er viktig å skrive navn som:

  • Avslør intensjonen
  • Unngå desinformasjon
  • Er lett søkbare

Når det gjelder å navngi klasser og funksjoner, er et godt triks å bruke et substantiv eller substantiv-setning for klasser og brukerverb eller verbfrasenavn for metoder.

Også når du bruker forskjellige designmønstre, er det noen ganger bra å legge til mønsternavnene som Kommando eller besøkende i klassenavnet. Så leseren ville umiddelbart vite hvilket mønster som brukes der uten å ha behov for å lese hele koden for å finne ut om det.

Bruker SRP

En annen ting som gjør kodekspressivt er å bruke Single Responsibility Principle som ble nevnt ovenfra. Du kan uttrykke deg ved å holde funksjonene og klassene dine små og for et enkelt formål. Små klasser og funksjoner er vanligvis enkle å navngi, enkle å skrive og enkle å forstå. En funksjon skal bare tjene til ett formål.

Skriveprøve

Å skrive tester bringer også mye klarhet, spesielt når du jobber med legacy code. Velskrevne enhetstester er også uttrykksfulle. Et hovedmål med testene er å fungere som dokumentasjon ved eksempel. Noen som leser testene våre, skal kunne få en rask forståelse av hva en klasse handler om.

Minimer antall klasser og metoder

Funksjonene til en klasse må forbli korte, en funksjon skal alltid utføre bare en ting. Hvis en funksjon har for mange linjer som det kan være tilfelle at den utfører handlinger som kan deles i to eller flere separate funksjoner.

En god tilnærming er å telle fysiske linjer og prøve å sikte mot maksimalt fire til seks linjer med funksjoner, i de fleste tilfeller alt som går mer enn det antall linjer det kan bli vanskelig å lese og vedlikeholde.

En god idé i iOS er å kutte konfigurasjonssamtalene som vi vanligvis gjør på viewDidLoad eller viewDidAppear-funksjoner.

På denne måten vil hver av funksjonene være små og vedlikeholdbare i stedet for en rot viewDidLoad-funksjon. Det samme bør også gjelde for appdelegat. Vi bør unngå å kaste hver konfigurasjon ondidFinishLaunchingWithOptions-metoden og separate konfigurasjonsfunksjoner eller enda bedre konfigurasjonsklasser.

Med funksjoner er det litt enklere å måle om vi holder det lenge eller kort, vi kan mesteparten av tiden bare stole på å telle de fysiske linjene. Med klasser bruker vi et annet tiltak. Vi teller ansvar. Hvis en klasse bare har fem metoder, betyr det ikke at klassen er liten, det kan være at den har for mange ansvarsområder med bare disse metodene.

Et kjent problem i iOS er den store størrelsen på UIViewControllers. Det er sant at med apple view controller-design er det vanskelig å holde disse objektene til å tjene et enkelt formål, men vi bør prøve vårt beste.

Det er mange måter å gjøre UIViewControllers små på. Min preferanse er å bruke en arkitektur som har bedre separasjon av bekymringer noe som VIPER eller MVP, men det betyr ikke at vi ikke kan gjøre det bedre i apple MVC også.

Ved å prøve å skille så mange bekymringer kan vi nå ganske anstendig kode med hvilken som helst arkitektur. Tanken er å lage klasser med ett formål som kan tjene som hjelpere for visningskontrollørene og gjøre koden mer lesbar og testbar.

Noen ting som ganske enkelt kan unngås uten unnskyldning i synskontrollere er:

  • I stedet for å skrive nettverkskode direkte skal det være en NetworkManager en klasse som er ansvarlig for nettverkssamtaler
  • I stedet for å manipulere data i visningskontrollere kan vi ganske enkelt opprette en DataManager en klasse som er ansvarlig for det.
  • I stedet for å spille med UserDefaults strenger i UIViewController kan vi lage en fasade over det.

For å konkludere

Jeg mener at vi bør komponere programvare fra komponenter som er nøyaktig navngitt, enkle, små, ansvarlige for en ting og kan brukes på nytt.

I denne artikkelen diskuterte vi fire regler for enkel design av Kent Beck og ga praktiske eksempler på hvordan vi kan implementere dem i iOS-utviklingsmiljø.

Hvis du likte denne artikkelen, må du huske å klaffe for å vise din støtte. Følg meg for å se mange flere artikler som kan ta dine iOS-utviklerferdigheter til et neste nivå.

Hvis du har spørsmål eller kommentarer, legg gjerne igjen en lapp her eller mail meg på arlindaliu.dev@gmail.com.