Penso che sia necessario fare una chiara distinzione tra design dell'applicazione e progettazione del sistema (ideando l'architettura di un'applicazione).
Il design dell'applicazione è ciò che vedi dall'esterno. Riguarda il design delle caratteristiche, la progettazione dell'esperienza utente e persino le decisioni di marketing (strategie di implementazione e monetizzazione). Ovviamente il design delle applicazioni non può ignorare i limiti tecnici, ma tenerne conto è la parte in cui quasi tutti hanno ragione, mentre tutto il resto è, quante persone sbagliano.
Il design del sistema è probabilmente ciò che intendi. Riguarda la dissezione di un'applicazione in unità indipendenti (livelli, servizi e altri moduli), in base alla progettazione dell'applicazione e quindi reiterando questo passaggio fino a quando non vengono lasciate unità gestibili.
La chiave per la progettazione flessibile del sistema è l'accoppiamento basso, che si ottiene nascondendo le informazioni, una parte è l'incapsulamento (cioè legando dettagli di implementazione cattivi e fragili in oggetti robusti) e l'altro astrazione (ovvero scegliere di non dipendere da un modulo concreto, ma la più piccola astrazione possibile di esso).
Ad esempio, se si dispone di un DataStore di classe, fornendo accesso agli utenti e alle immagini memorizzate, agendo quindi come IUserStore e come IPictureStore, si desidera che i singoli renderer dipendano da tali astrazioni e inject l'implementazione. Perché a un certo punto potresti decidere di mantenere i tuoi dati utente sul tuo server, ma di memorizzare le tue foto con alcuni servizi cloud di terze parti, il che significa che dividere il DataStore in due classi potrebbe essere necessario. Tutti i componenti che dipendono esclusivamente dalle astrazioni non sono interessati.
Quello che vuoi è un sistema, in cui singoli componenti possono essere modificati in isolamento, senza effetti sul sistema.
L'unica domanda che ti rimane da rispondere è quando considerare un componente stesso come un sistema e per modularlo ulteriormente e quando fermarsi. Il SRP è una regola radicale, ma è difficile da rispettare.
Preferisco un approccio più pragmatico. Finché un componente è facile da afferrare, lasciatelo così com'è. Quando cresce oltre un punto, dove puoi capirlo entro un minuto o reimplementarlo in mezza giornata, è il momento di individuare e isolare le responsabilità individuali.
Ma questa è una linea da disegnare per te, ed è qualcosa che accade naturalmente sul campo.
Quindi riassumendo:
- Non astrarre prima dei requisiti. Usa le astrazioni per esprimere i requisiti in modo chiaro ed esatto, mantenendoli semplici ed evidenti.
- Non modularizzare prima della complessità. Naturalmente mantieni insieme ciò che è semplice e analizza ciò che è complesso.
- Non implementare prima delle astrazioni. Non fare supposizioni inutili, dipende dalle astrazioni, piuttosto che dai dettagli.