Learn Software Development

February 28, 2025

In questo articolo parleremo di:

  • Build Systems
  • Stile e formattazione del codice
  • Testing
  • MVC - Model-View-Controller
  • VCS - Version Control Systems
  • AGILE SOFTWARE DEVELOPMENT

Build Systems

La costruzione del software include:

  • Manipolazione e pre-processing del sorgente
  • Verifica della qualità del codice
  • Gestione delle dipendenze
  • Compilazione
  • Testing e misurazione della copertura
  • Generazione della documentazione

Build automation

  1. Imperativo/Personalizzato:

    • Realizzato con script di programmazione
    • Flessibile ma difficile da adattare/riusare
  2. Dichiarativo/Standardizzato:

    • Usa file di configurazione dedicati
    • Portabile e comprensibile, ma limitato dalle opzioni disponibili

Convention over Configuration

  • Standard di fatto
  • Riduce le ripetizioni
  • Aumenta la portabilità

Automatori ibridi

Sono sistemi che cercano di combinare il meglio dei sistemi dichiarativi e imperativi.

Esempi:

  • Sbt, che si appoggia su Scala
  • Gradle, che si appoggia su Groovy

Domain-Specific languages

Essere flessibili da permettere dicostruire un linguaggio dentro al linguaggio.

Gradle

  • Usa Kotlin o Groovy
  • Build system Ibrido
  • Supporta Java, C/C++, Scala, Kotlin
  • Usato nell'ecosistema Android

Concetti fondamentali:

  1. Progetto, contiene build.gradle.kts (logica di costruzione) e/o settings.gradle.kts (nome al progetto), detti build file.
  2. Task, Operazione atomica del processo di build
  3. Plugin, task pronti all'uso

Dipendeza del software:

  • Le applicazioni hanno un albero di dipendenze!

Dipendendeze transitive:

  • Sono le dipendenze indirette (dipendenze delle dipendenze).

Maven

Java non definisce alcuno standard per i nomi di libreria, per sopperire a questo problema è nato Maven.

che è poi divento lo standard de facto per la gestione delle dipendenze in Java nei build system.

  • Maven Central Repository (Sonatype OSSRH)

Maven naming convention: È il repository di default di Maven, contiene milioni di librerie.

  • groupId: identifica progetto/gruppo (es: com.google)
  • artifactId: identifica libreria specifica
  • version: versione della libreria

Gestione delle dipendenze in gradle

Grande consente di gestirle, potremmo avere anche delle librerie da utilizzare solo per i test, che poi non useremo in produzione.

plugins { java } // Carica il necessario per Java
repositories { mavenCentral() } // Configura Gradle per cercare e scaricare da Maven Central

Trovare su Maven Central la libreria che intendiamo usare e annotare:

  • groupId
  • artifactId
  • version

Gradle consente di (costringe a) dire chiaramente “a cosa serve” una certa libreria:

  • implementation: sia per compilare che per eseguire il codice
  • testImplementation: compilare e eseguire i test
  • testRuntimeOnly: solo eseguzione test

Lanciare app java da Gradle

plugins {
    java // Carica il necessario per la compilazione di Java
    application // Carica il necessario per l'esecuzione di classi con la JVM
}
repositories { mavenCentral() } // Configura Gradle per cercare e scaricare da Maven Central
dependencies { // Elenco di librerie che ci servono
    implementation("com.omertron:API-OMDB:1.5") //libreria
}
application { // Configurazione dell'esecuzione, la main class è sufficiente
    mainClass.set("it.unibo.sampleapp.SimplerRateAMovie")
}

GRADLE WRAPPER

Caratteristiche:

  1. Garantisce consistenza della versione di Gradle
  2. Permette coesistenza di diverse versioni
  3. Automatizza download e installazione

Componenti:

  • gradlew (script Unix)
  • gradlew.bat (script Windows)
  • gradle/wrapper/gradle-wrapper.properties
  • gradle/wrapper/gradle-wrapper.jar

Vantaggi:

  • Non richiede installazione manuale di Gradle
  • Garantisce compatibilità tra sviluppatori
  • Semplifica la gestione di progetti multipli

Comandi principali:

  • ./gradlew build - compila il progetto
  • ./gradlew test - esegue i test
  • ./gradlew clean - pulisce la build
  • ./gradlew tasks - mostra i task disponibili

Stile e formattazione del codice

Importanza di uno stile uniforme, consistente, Standard.

  • Indentazione 4 caratteri
  • Lunghezza linee non più di 90 caratteri

Commenti:

  • // ... su una linea
  • /_ ... _/ su più linee per commentare sezioni
  • /*_ ... _/ su più linee per commenti che generano documentazione

Utili:

  • Definizione di variabile: una per linea, meglio se a inizio del metodo
  • Meglio inizializzare sempre le variabili!
  • Una sola istruzione per riga
  • Blocchi e parentesi graffe, anche per blocchi ad uno statement

Nomi:

  • Classi (e interface): PascalCase
  • Metodi, campi, variabili: camelCase
  • Package: lowercase
  • Campi costanti: UPPERCASE

Ordine degli elementi della classe:

  • Campi statici (pubblici, poi privati – ovvero, dal più aperto al più chiuso)
  • Campi istanza (pubblici, poi privati)
  • Costruttori (pubblici, poi privati)
  • Metodi (raggruppati per ruolo, NON per Ûsibilità, NON alfabeticamente)

Metodi getter/setter:

  • Un metodo getter è un metodo che senza input restituisce un valore, una proprietà dell’oggetto
  • Un metodo setter è un metodo che restituisce void e accetta un valore che modifica una proprietà dell’oggetto

Convenzioni nel nome:

- Getter non booleano: T getXYZ(){...}
- Getter booleano: boolean getXYZ(){...} o anche boolean isXYZ() {...}
- Setter: void setXYZ() {...}

Esempio lamp

public class Lamp {
    ...
    // Setter per proprieta' Intensity di tipo double
    public void setIntensity(double value) {
    ...
    } /
    / Getter per proprieta' Intensity di tipo double
    public double getIntensity() {
    ...
    } /
    / Getter per proprieta' SwitchedOn di tipo boolean
    public boolean isSwitchedOn() {
    ...
    }
}

lampUtilities

public class LampUtilities {
    /* Costruisco una fila di lampadine */
    public static Lamp[] buildLampRow(final int size) {
        final Lamp[] array = new Lamp[size];
        for (int index = 0; index < size; index++) {
        array[index] = new Lamp();
        }
        return array;
    } /
    * Accendo/spengo una fila di lampadine */
    public static void switchAll(final boolean switchedOn, final Lamp[] array) {
        for (Lamp l : array) {
        if (switchedOn) {
            l.switchOn();
        } else {
            l.switchOff();
        }
    }
    }
}

Checkstyle

  • Si occupa di trovare errori di stile e formattazione del codice.
  • Si dichiara lo stile desiderato in un file checkstyle.xml e poi si esegue il comando.

Si integra anche con Gradle e poi di defualt fallisce la build se non si rispettano le regole.

Testing

Il testing del software è quell’attività di ricerca di anomalie al fine di localizzare e rimuovere i difetti nel software

Definizioni:

  • Errore: l’azione che ha causato una falla
  • Falla (bug): causa di un’anomalia
  • Fallimento (anomalia): differenza tra risultato atteso e attuale
  • Testing: attività di ricerca anomalie per localizzare/rimuovere difetti

Errore -> Difetto/Bug -> Fallimento

  • Verifica: controllo correttezza rispetto a specifiche
  • Validazione: controllo adeguatezza rispetto aspettative stakeholder

JUNIT 5: FRAMEWORK DI TESTING

UUT: unit under test

Architettura JUnit 5:

  • JUnit Platform: piattaforma per esecuzione test
  • JUnit Jupiter: API per scrivere test
  • JUnit Vintage: supporto per test JUnit 4

Pattern AAA:

  • Arrange: setup del contesto del test
  • Act: esecuzione della funzionalità da testare
  • Assert: verifica del risultato

Esempio di asserizone:

per confrontare il risultato atteso con quello effettivo:

assertEquals(expected, actual)
assertTrue(condition)
assertFalse(condition)
assertNull(object)
assertNotNull(object)
assertArrayEquals(expectedArray, actualArray)

Annotazioni principali:

Le annotazione sono informazioni aggiuntive ai costrutti del programma

  • @Test: marca un metodo come test ( può espiremere uno più test case)
  • @BeforeEach: eseguito prima di ogni test
  • @AfterEach: eseguito dopo ogni test
  • @BeforeAll: eseguito una volta prima di tutti i test
  • @AfterAll: eseguito una volta dopo tutti i test

Unit testing: la pratica di testare una unità funzionale in isolamento (funzioni, metodi, classi).

  • Esempio: testare una funzione che calcola lo sconto su un prezzo

Integration testing: verificare la correttezza dell’integrazione di diverse unità

  • Esempio: testare l'integrazione tra il modulo ordini e il modulo pagamenti

TDD

  1. RED: si scrive un test per catturare la funzionalità che si vuole realizzare (visto che è ancora da implementare, questa fallirà)
  2. GREEN: si implementa la funzionalità fino a che il test passa
  3. REFACTOR: eventualmente, si migliora l’implementazione rieseguendo i test, sempre sicuri che questi interventi non causano regressioni

Terminata un’iterazione RED-GREEN-REFACTOR, si può procedere a un’altra iterazione, ovvero alla realizzazione di un incremento di funzionalità

Principi fondamentali:

  • Test isolati e indipendenti
  • Un assert per test (quando possibile)
  • Test deterministici
  • No logica nei test

MVC - Model-View-Controller

Modello architetturale che divide un'applicazione in tre componenti principali per separare le responsabilità.

Per esempio, in un'app di note:

  • Model: gestisce il salvataggio e modifica delle note
  • View: mostra la lista delle note e il form per crearne di nuove
  • Controller: gestisce le azioni come "crea nota" o "elimina nota"

Promuovere il riuso, la modularità, l’estensibilità, la manutenibilità del software

Model: metodi di dominio chiamati da Controller

incapsula dati e logica relativi al dominio della applicazione

  • Modello del dominio applicativo
  • Incapsula dati e logica di business
  • Indipendente da visualizzazione e controllo

View: metodi void chiamati da View (azioni utente)

incapsula la GUI, le sue sottoparti, e la logica di notifica

  • Gestisce interazioni con l'utente
  • Si occupa di input e output
  • Presenta i dati del Model
  • Notifica il Controller delle azioni utente

Controller: metodi void chiamati da Controller (richieste visualizzazione)

intercetta gli eventi della View, comanda le modi×che al modello, cambia di conseguenza la View

  • Coordina Model e View
  • Intercetta eventi dalla View
  • Comanda modifiche al Model
  • Aggiorna la View in base ai cambiamenti

ESEMPIO PRATICO (DrawNumber):

// Model Interface
public interface DrawNumber {
    void reset();
    DrawResult attempt(int n);
}

// View Interface
public interface DrawNumberView {
    void setObserver(DrawNumberViewObserver observer);
    void start();
    void numberIncorrect();
    void result(DrawResult res);
}

// Controller (observer)
public interface DrawNumberViewObserver {
    void newAttempt(int n);
    void resetGame();
    void quit();
}

VANTAGGI DEL PATTERN

  • Separazione delle responsabilità
  • Facilità di testing (componenti testabili separatamente)
  • Possibilità di modificare un componente senza impattare gli altri
  • Supporto al lavoro in team
  • Codice più organizzato e mantenibile

VCS - Version Control Systems

Evoluzione del controllo versione:

  • Centralizzato
  • Distribuito

Centralizzato:

  • Concurrent Versioning System (CVS) - 1986
  • Apache Subversion (SVN)

Pain:

  • Single Point of Failure
  • Connettività, necessaria la connessione per quasi tutte le operazioni
  • Difficile collaborazione
  • Performance

Distribuito DVCS(Distributed Version Control System):

  • Git - 2005
  • Mercurial

Scopo:

  • Gestione delle modifiche
  • Collaborazione
  • Tracciabilità

Tracking delle modifiche:

  • Evitare perdita di progressi
  • Documentare lo sviluppo
  • Possibilità di rollback

Collaborazione:

  • Lavoro concorrente
  • Integrazione modifiche
  • Risoluzione conflitti

Git

Repository: Contiene intero progetto/storia e metadata

Commit: Snapshot dello stato del progetto

Best Praticies:

  • Commit piccoli e frequenti
  • Un branch per feature
  • Main branch sempre stabile
  • Pull prima del lavoro

Configurazione Inizale:

# Setup identità
git config --global user.name "Nome"
git config --global user.email "email"
git config --global init.defaultbranch main

Operazioni Base Repository

# Inizializzazione e setup
git init
git remote add origin URL
git clone URL

Gestione Modifiche

# Stage e commit
git add .           # Stage tutte le modifiche
git add <file>      # Stage file specifico
git commit -m "messaggio"
git reset HEAD <file>

Sincronizzazione

# Comandi remote
git fetch           # Scarica modifiche
git pull            # Scarica e merge
git push            # Carica modifiche

Branch Management

# Gestione branch
git branch nome     # Crea branch
git checkout nome   # Switch branch
git checkout -b new # Crea e switch
git merge branch    # Unisce branch
git rebase main     # Rebase su main

Monitoraggio

# Stato e storia
git status          # Stato corrente
git log             # Storia commit
git diff            # Mostra modifiche

GitHub

  • Servizio di hosting per repository Git

Gitignore: File .gitignore per escludere files

AGILE SOFTWARE DEVELOPMENT

  • Insieme principi/pratiche per rispondere al cambiamento
  • Approcci iterativi/incrementali
  • Cicli brevi per feedback frequente
  • Focus sulla qualità

Manifesto Agile

Valorizzare:

  • Individui e interazioni più che processi e strumenti
  • Software funzionante più che documentazione esaustiva
  • Collaborazione col cliente più che negoziazione contratti
  • Rispondere al cambiamento più che seguire un piano"

See you in the next post! Stay Tuned!