Quattro regole di progettazione software iOS più semplice

Alla fine degli anni '90, mentre sviluppava Extreme Programming, il famoso sviluppatore di software Kent Beck inventò un elenco di regole per la progettazione di software semplice.

Secondo Kent Beck, un buon software design:

  • Esegue tutti i test
  • Non contiene duplicati
  • Esprime l'intenzione del programmatore
  • Riduce al minimo il numero di classi e metodi

In questo articolo, discuteremo di come queste regole possano essere applicate al mondo dello sviluppo di iOS, fornendo esempi pratici di iOS e discutendo come trarne vantaggio.

Esegue tutti i test

La progettazione del software ci aiuta a creare un sistema che agisce come previsto. Ma come possiamo verificare che un sistema agirà come previsto inizialmente dal suo design? La risposta è creando test che lo convalidano.

Sfortunatamente, nell'universo di sviluppo iOS i test sono spesso evitati ... Ma per creare un software ben progettato, dovremmo sempre scrivere codice Swift tenendo presente la testabilità.

Discutiamo di due principi che possono semplificare la scrittura di test e la progettazione di sistemi. E sono il principio di responsabilità singola e l'iniezione di dipendenza.

Principio di responsabilità unico (SRP)

SRP afferma che una classe dovrebbe avere una e una sola ragione per cambiare. L'SRP è uno dei principi più semplici e uno dei più difficili da ottenere. Mescolare le responsabilità è qualcosa che facciamo naturalmente.

Forniamo un esempio di un codice che è davvero difficile da testare e, successivamente, rielaborarlo utilizzando SRP. Quindi discutere su come ha reso testabile il codice.

Supponiamo che attualmente dobbiamo presentare un PaymentViewController dal nostro attuale controller di visualizzazione, PaymentViewController dovrebbe configurarne la vista in base al prezzo del nostro prodotto di pagamento. Nel nostro caso, il prezzo è variabile a seconda di alcuni eventi di utenti esterni.

Il codice per questa implementazione è attualmente simile al seguente:

Come possiamo testare questo codice? Cosa dovremmo testare prima? Lo sconto sul prezzo è calcolato correttamente? Come possiamo deridere gli eventi di pagamento per testare lo sconto?

Scrivere test per questa classe sarebbe complicato, dovremmo trovare un modo migliore per scriverlo. Bene, innanzitutto affrontiamo il grosso problema. Dobbiamo districare le nostre dipendenze.

Vediamo che abbiamo una logica per caricare il nostro prodotto. Abbiamo eventi di pagamento che rendono l'utente idoneo per uno sconto. Abbiamo sconti, un calcolo di sconto e l'elenco continua.

Quindi proviamo semplicemente a tradurli in codice Swift.

Abbiamo creato un PaymentManager che gestisce la nostra logica relativa ai pagamenti e un PriceCalculator separato che è facilmente testabile. Inoltre, un caricatore di dati responsabile dell'interazione della rete o del database per il caricamento dei nostri prodotti.

Abbiamo anche detto che abbiamo bisogno di una classe responsabile della gestione degli sconti. Chiamiamolo CouponManager e gestiamo anche i coupon di sconto utente.

Il nostro controller della vista di pagamento può quindi apparire come il seguente:

Ora possiamo scrivere test come

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

e molti altri! Creando oggetti separati evitiamo duplicazioni non necessarie e creiamo anche un codice per il quale è facile scrivere test.

Iniezione di dipendenza

Il secondo principio è l'iniezione di dipendenza. E dagli esempi sopra abbiamo visto che abbiamo già usato l'iniezione di dipendenza sui nostri inizializzatori di oggetti.

Ci sono due principali vantaggi nell'iniettare le nostre dipendenze come sopra. Ciò chiarisce su quali dipendenze fanno affidamento i nostri tipi e ci consente di inserire oggetti finti quando vogliamo testare invece di quelli reali.

Una buona tecnica è quella di creare protocolli per i nostri oggetti e fornire un'implementazione concreta dell'oggetto reale e fittizio come il seguente:

Ora possiamo facilmente decidere quale classe vogliamo iniettare come dipendenza.

L'accoppiamento stretto rende difficile la scrittura di test. Allo stesso modo, più test scriviamo, più usiamo principi come DIP e strumenti come iniezione di dipendenza, interfacce e astrazione per minimizzare l'accoppiamento.

Rendere il codice più testabile non solo elimina la nostra paura di romperlo (poiché scriveremo il test che ci supporterà), ma contribuisce anche a scrivere codice più pulito.

Questa parte dell'articolo riguardava più la modalità di scrittura del codice che sarà testabile che la scrittura del test unitario effettivo. Se vuoi saperne di più sulla scrittura del test unitario, puoi leggere questo articolo in cui creo il gioco della vita usando lo sviluppo test-driven.

Non contiene duplicati

La duplicazione è il principale nemico di un sistema ben progettato. Rappresenta lavoro aggiuntivo, rischio aggiuntivo, aggiunge complessità non necessaria.

In questa sezione discuteremo di come utilizzare il modello di progettazione dei modelli per rimuovere duplicazioni comuni in iOS. Al fine di rendere più facile la comprensione, stiamo andando a riformattare l'implementazione di una chat nella vita reale.

Supponiamo di avere attualmente nella nostra app una sezione di chat standard. Si presenta un nuovo requisito e ora vogliamo implementare un nuovo tipo di chat: una live chat. Una chat che dovrebbe contenere messaggi con un numero massimo di 20 caratteri e questa chat scomparirà quando chiudiamo la visualizzazione della chat.

Questa chat avrà le stesse viste della nostra chat corrente ma avrà alcune regole diverse:

  1. La richiesta di rete per l'invio di messaggi di chat sarà diversa.

2. I messaggi di chat devono essere brevi, non più di 20 caratteri per il messaggio.

3. I messaggi di chat non devono essere persistenti nel nostro database locale.

Supponiamo che stiamo utilizzando l'architettura MVP e attualmente gestiamo la logica per l'invio di messaggi di chat nel nostro presentatore. Proviamo ad aggiungere nuove regole per il nostro nuovo tipo di chat chiamato live-chat.

Un'implementazione ingenua sarebbe come la seguente:

Ma cosa succede se in futuro avremo molti più tipi di chat?
Se continuiamo ad aggiungere se altro che controlla lo stato della nostra chat in ogni funzione, il codice diventerà difficile da leggere e mantenere. Inoltre, è difficilmente testabile e il controllo dello stato sarebbe duplicato in tutto l'ambito del presentatore.

È qui che viene utilizzato il modello di modello. Il modello di modello viene utilizzato quando sono necessarie più implementazioni di un algoritmo. Il modello viene definito e quindi sviluppato con ulteriori variazioni. Utilizzare questo metodo quando la maggior parte delle sottoclassi deve implementare lo stesso comportamento.

Siamo in grado di creare un protocollo per Chat Presenter e separiamo i metodi che verranno implementati in modo diverso da oggetti concreti nelle fasi di Presenter chat.

Ora possiamo rendere il nostro presentatore conforme a IChatPresenter

Il nostro presentatore ora gestisce l'invio di messaggi chiamando funzioni comuni al suo interno e delega le funzioni che possono essere implementate in modo diverso.

Ora siamo in grado di fornire oggetti Create conformi alle fasi del presentatore in base e configurare queste funzioni in base alle loro esigenze.

Se utilizziamo l'iniezione delle dipendenze nel nostro controller della vista, ora possiamo riutilizzare lo stesso controller della vista in due casi diversi.

Utilizzando Design Patterns possiamo davvero semplificare il nostro codice iOS. Se vuoi saperne di più, il seguente articolo fornisce ulteriori spiegazioni.

Espressive

La maggior parte dei costi di un progetto software riguarda la manutenzione a lungo termine. Scrivere codice di facile lettura e manutenzione è un must per gli sviluppatori di software.

Possiamo offrire un codice più espressivo usando un buon nome, usando SRP e test di scrittura.

Naming

La prima cosa che rende il codice più espressivo - ed è la denominazione. È importante scrivere nomi che:

  • Rivela l'intenzione
  • Evita la disinformazione
  • Sono facilmente ricercabili

Quando si tratta di nominare classi e funzioni, un buon trucco è usare un nome o una frase-sostantivo per classi e verbi utente o nomi di frasi verbali per metodi.

Inoltre, quando si utilizzano modelli di design diversi, a volte è utile aggiungere i nomi dei pattern come Command o Visitor nel nome della classe. Quindi il lettore saprebbe immediatamente quale modello viene utilizzato lì senza la necessità di leggere tutto il codice per scoprirlo.

Utilizzando SRP

Un'altra cosa che rende il codice espressivo è l'utilizzo del principio di responsabilità singola menzionato sopra. Puoi esprimerti mantenendo le tue funzioni e classi piccole e per un unico scopo. Le piccole classi e funzioni sono generalmente facili da nominare, facili da scrivere e facili da capire. Una funzione dovrebbe servire solo per uno scopo.

Test di scrittura

Scrivere test porta anche molta chiarezza, specialmente quando si lavora con il codice legacy. Anche i test unitari ben scritti sono espressivi. Un obiettivo primario dei test è di fungere da documentazione per esempio. Qualcuno che legge i nostri test dovrebbe essere in grado di comprendere rapidamente di cosa tratta una classe.

Ridurre al minimo il numero di classi e metodi

Le funzioni di una classe devono rimanere brevi, una funzione dovrebbe sempre svolgere solo una cosa. Se una funzione ha troppe righe, è possibile che stia eseguendo azioni che possono essere separate in due o più funzioni separate.

Un buon approccio è contare le linee fisiche e cercare di puntare al massimo da quattro a sei linee di funzioni, nella maggior parte dei casi tutto ciò che va oltre quel numero di righe può essere difficile da leggere e mantenere.

Una buona idea in iOS è tagliare le chiamate di configurazione che di solito facciamo sulle funzioni viewDidLoad o viewDidAppear.

In questo modo, ognuna delle funzioni sarebbe piccola e gestibile anziché una funzione disordine viewDidLoad. Lo stesso dovrebbe valere anche per i delegati delle app. Dovremmo evitare di lanciare ogni configurazione ondidFinishLaunchingWithOptions e funzioni di configurazione separate o classi di configurazione ancora migliori.

Con le funzioni, è un po 'più facile misurare se lo manteniamo lungo o corto, la maggior parte delle volte possiamo semplicemente contare sul conteggio delle linee fisiche. Con le classi, usiamo una misura diversa. Contiamo le responsabilità. Se una classe ha solo cinque metodi, ciò non significa che la classe sia piccola, potrebbe essere che abbia troppe responsabilità solo con quei metodi.

Un problema noto in iOS è la grande dimensione di UIViewController. È vero che dal design del controller Apple View è difficile mantenere questi oggetti per un solo scopo, ma dovremmo fare del nostro meglio.

Esistono molti modi per ridurre le dimensioni di UIViewController, la mia preferenza è quella di utilizzare un'architettura che abbia una migliore separazione delle preoccupazioni, come VIPER o MVP, ma ciò non significa che non possiamo migliorarlo anche in Apple MVC.

Cercando di separare altrettante preoccupazioni possiamo raggiungere un codice abbastanza decente con qualsiasi architettura. L'idea è quella di creare classi monouso che possano servire come aiutanti per i controller di visualizzazione e rendere il codice più leggibile e testabile.

Alcune cose che possono essere semplicemente evitate senza scuse nei controller di visualizzazione sono:

  • Invece di scrivere direttamente il codice di rete dovrebbe esserci un NetworkManager una classe responsabile delle chiamate di rete
  • Invece di manipolare i dati nei controller di visualizzazione, possiamo semplicemente creare un DataManager di una classe responsabile.
  • Invece di giocare con le stringhe UserDefaults in UIViewController, possiamo creare una facciata sopra quella.

In conclusione

Credo che dovremmo comporre il software da componenti accuratamente denominati, semplici, piccoli, responsabili di una cosa e riutilizzabili.

In questo articolo, abbiamo discusso quattro regole per la progettazione semplice di Kent Beck e abbiamo fornito esempi pratici di come possiamo implementarle nell'ambiente di sviluppo iOS.

Se ti è piaciuto questo articolo assicurati di applaudire per mostrare il tuo supporto. Seguimi per visualizzare molti altri articoli che possono portare le tue abilità di sviluppatore iOS a un livello successivo.

In caso di domande o commenti, non esitare a lasciare una nota qui o inviarmi un'e-mail all'indirizzo arlindaliu.dev@gmail.com.