REFACTORING CHECKREGOLE FASE DI UPLOAD Code refactoring: cos’è e perché va fatto Il code refactoring è un processo di ristrutturazione del codice il quale principale scopo è quello di migliorare la leggibilità, prestazioni e manutenibilità nel tempo. Programmazione funzionale Paradigma di programmazione in cui il flusso di esecuzione del programma assume la forma di una serie di valutazioni di funzioni matematiche. (Wikipedia) Pone maggior accento sulle funzioni rispetto ai paradigmi procedurali e imperativi. Esempio: Approccio imperativo List<Persona> femmine = new ArrayList<>(); for (Persona persona: persone) { if (FEMMINA.equals(persona.getGenere())) { femmine.add(persona); } } for (Persona femmina : femmine) { System.out.prinln(femmina); } Approccio dichiarativo persone.stream(). .filter(persona -> FEMMINA.equals(persona.getGenere())) .collect.(Collectors.toList()) .forEach(System.out::println); La programmazione funzionale è una sorta di sottoinsieme della programmazione dichiarativa. Espressione Lambda Un’espressione lambda è una funzione anonima. I parametri in ingresso sono a sinistra del segno freccia, e il corpo della funzione è a destra, è simile ad un metodo. Esempi: s -> System.out.println("Ciao "+ s); (x, y) -> x + y; Cosa è stato fatto in questo caso Prima del refactoring avevamo questa situazione: REFACTORING CHECKREGOLE FASE DI UPLOAD 1 protected EsitoUpload checkRegole(CeStagingUpload ceStagingUpload) { LOG.info("START Check regole - proess CeStagingUpload con idStagingUpload {} ", ceStagingUpload.getIdStagingUpload()); EsitoUpload esitoUpload = new EsitoUpload(); esitoUpload.setEsito(ESITO_OK); esitoUpload.setNotaEsito(""); LOG.info("esitoUpload {} ", esitoUpload.toString()); esitoUpload = checkCodPortafoglio(ceStagingUpload, esitoUpload); LOG.info("esitoUpload checkCodPortafoglio {} ", esitoUpload.toString()); if (ESITO_KO.equalsIgnoreCase(esitoUpload.getEsito())) { return esitoUpload; } esitoUpload = checkCambioIfrs(ceStagingUpload, esitoUpload); LOG.info("esitoUpload checkCambioIfrs{} ", esitoUpload.toString()); if (ESITO_KO.equalsIgnoreCase(esitoUpload.getEsito())) { return esitoUpload; } esitoUpload = checkRapportoFittizio(ceStagingUpload, esitoUpload); LOG.info("esitoUpload checkRapportoFittizio{} ", esitoUpload.toString()); if (ESITO_KO.equalsIgnoreCase(esitoUpload.getEsito())) { return esitoUpload; } ... per altre 15 regole da applicare al nostro oggetto "esitoUpload" } Siamo quindi passati da un approccio imperativo ad un approccio funzionale nel seguente modo: Ho dichiarato come attributi di classe le regole e l’esito dell’upload private EsitoUpload esitoUpload = new EsitoUpload(); private Supplier<Stream<Function>> regole = () -> Stream.of( ceStagingUpload -> this.checkCodPortafoglio((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkCambioIfrs((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkRapportoFittizio((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkFase((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkSingleName((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkMotivazione((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkAbi((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkIfrs5Chiuso((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkIfrs5ApertoIfrs9((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.getCeHermioneAgg((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkFilialeRapporto((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkNsgWithoutFiliale((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.getPortafogliAttiviWithFilialeRapporto((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.getPortafogliAttiviWithNsg((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkRapportoNotExists((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkFlagForzatura((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkProgRichiesta1((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkProgRichiesta2((CeStagingUpload) ceStagingUpload), ceStagingUpload -> this.checkFilialeRapportoIfrs9((CeStagingUpload) ceStagingUpload) ); } Analizziamo le classi utilizzate: Stream: rappresenta una sequenza di elementi sui cui eseguire operazioni intermedie o terminali. Le terminali generano un risultato di un determinato tipo mentre le intermedie un nuovo Stream su cui invocare altre operazioni dando vita ad una catena di chiamate a metodi. Supplier: rappresenta un fornitore di risultati. Espone un solo metodo (essendo un’interfaccia funzionale) “get()” che produce un risultato senza accettare input. Il suo funzionamento in questo caso ci torna utile in fase di test perché riutilizzando più di una volta lo stream dichiarato sopra ci lancia una IllegalStateException: “stream has already been operated upon or closed”. Questo avviene perché lo stream in Java 8 alla fine dell’ultima operazione REFACTORING CHECKREGOLE FASE DI UPLOAD 2 viene chiuso e quindi non riutilizzabile. La classe Supplier ci risolve il problema perché utilizzando il metodo get() ogni volta restituirà un nuovo stream. Function: rappresenta una funzione che prende in input un solo argomento e produce un risultato. Il suo metodo funzionale è apply(T input) applica la funzione all’argomento dato in input. Corpo del metodo dopo il refactoring: protected EsitoUpload checkRegole(CeStagingUpload ceStagingUpload) { LOG.info("START Check regole - proess CeStagingUpload con idStagingUpload {} ", ceStagingUpload.getIdStagingUpload()); this.esitoUpload.setEsito(ESITO_OK); this.esitoUpload.setNotaEsito(""); LOG.info("esitoUpload {} ", this.esitoUpload.toString()); this.regole.get().anyMatch(regola -> { regola.apply(ceStagingUpload); return ESITO_KO.equals(this.esitoUpload.getEsito()); }); return this.esitoUpload; } Analizziamo alcuni metodi utilizzato: get(), metodo dell’interfaccia funzionale Supplier, spiegato nell’analisi precedente. anyMatch(Predicate predicate), metodo della classe Stream che ritorna true se qualche elemento dello stream corrisponde al predicato fornito. apply(T input), analizzato prima, esegue la Function dello Stream dichiarato prima come attributo della classe, utilizza come input l’oggetto preso come parametro dal metodo checkRegole. Conclusione: Abbiamo visto come un approccio funzionale-dichiarativo può semplificare notevolmente la struttura del codice, rendendolo decisamente più leggibile, più manutenibile, più efficiente e facilmente scalabile. REFACTORING CHECKREGOLE FASE DI UPLOAD 3