From e8a71202ed003d68e87e6471f2f5ef56364b0a92 Mon Sep 17 00:00:00 2001 From: Colin DAMON <cdamon@ippon.fr> Date: Wed, 16 Dec 2020 15:07:41 +0100 Subject: [PATCH] Code and coffee on exceptions --- .gitlab-ci.yml | 1 + README.md | 1 + exceptions/.gitlab-ci.yml | 11 ++++ exceptions/pom.xml | 29 ++++++++++ exceptions/readme.md | 53 +++++++++++++++++++ .../java/fr/ippon/exception/Firstname.java | 14 +++++ .../MissingMandatoryValueException.java | 8 +++ .../fr/ippon/exception/ExceptionTest.java | 31 +++++++++++ 8 files changed, 148 insertions(+) create mode 100644 exceptions/.gitlab-ci.yml create mode 100644 exceptions/pom.xml create mode 100644 exceptions/readme.md create mode 100644 exceptions/src/main/java/fr/ippon/exception/Firstname.java create mode 100644 exceptions/src/main/java/fr/ippon/exception/MissingMandatoryValueException.java create mode 100644 exceptions/src/test/java/fr/ippon/exception/ExceptionTest.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 12434bf7..9b796639 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,3 +17,4 @@ include: - local: "/tcr-roman-numerals/.gitlab-ci.yml" - local: "/java-h2g2/.gitlab-ci.yml" - local: "/factory-patterns/.gitlab-ci.yml" + - local: "/exceptions/.gitlab-ci.yml" diff --git a/README.md b/README.md index e0a0cc93..56e2ac80 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré | [H2G2 en Java](/java-h2g2) | Kata | Moyenne | | [Back from GDCR avec Maxime, Séraphin, Anthony et Colin](https://www.youtube.com/watch?v=CHfUGdnSX6I) | Discussion | | | [Factory patterns](/factory-patterns) | Code&coffee | Facile | +| [Exceptions](/exceptions) | Code&coffee | Facile | diff --git a/exceptions/.gitlab-ci.yml b/exceptions/.gitlab-ci.yml new file mode 100644 index 00000000..fac25939 --- /dev/null +++ b/exceptions/.gitlab-ci.yml @@ -0,0 +1,11 @@ +package-exceptions: + variables: + PROJECT_FOLDER: "exceptions" + extends: .java + only: + refs: + - master + - merge_requests + changes: + - ".gitlab-common-ci.yml" + - "exceptions/**/*" diff --git a/exceptions/pom.xml b/exceptions/pom.xml new file mode 100644 index 00000000..95225079 --- /dev/null +++ b/exceptions/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <version>1.0.0</version> + <groupId>fr.ippon.kata</groupId> + <artifactId>java-parent</artifactId> + <relativePath>../java-parent</relativePath> + </parent> + + <version>1.0.0-SNAPSHOT</version> + <artifactId>exceptions</artifactId> + + <name>Exceptions</name> + + <developers> + <developer> + <email>arey@ippon.fr</email> + <name>Anthony REY</name> + </developer> + <developer> + <email>cdamon@ippon.fr</email> + <name>Colin DAMON</name> + </developer> + </developers> +</project> diff --git a/exceptions/readme.md b/exceptions/readme.md new file mode 100644 index 00000000..ec83e9f8 --- /dev/null +++ b/exceptions/readme.md @@ -0,0 +1,53 @@ +# Java exceptions + +Code & coffee sur les exceptions (sujet choisi par le chat) + +- **Auteurs** : Anthony REY et Colin DAMON +- **Date** : 16/12/2020 +- **Langage** : Java +- **Niveau** : Débutant +- **Replay** : TODO + +La prise en compte d'une exception doit être fait au plus proche de sa création. Il faut cependant être dans une couche permettant ce traitement : une exception peut donc traverser plusieurs "layers". + +## Exceptions en Java + +```plantuml +Throwable <|-- Exception +Throwable <|-- Error +Exception <|-- RuntimeException +``` + +## Liens dans le chat + +- [JSR](https://fr.wikipedia.org/wiki/Java_Specification_Requests) +- [Exceptions internationalisées](https://blog.ippon.fr/2020/07/22/exceptions-internationalisees/) +- [Des objets, pas des data classes](https://blog.ippon.fr/2020/04/01/des-objets-pas-des-data-classes/) +- [Hexagonal architecture](https://alistair.cockburn.us/hexagonal-architecture/) + +## Checked Exceptions + +Les checked exceptions doivent être explicitement `throw` ou `catch` ce qui a tendance à "bloquer" les mises à jour d'API. Si cela semblait être une bonne idée pour obliger le traitement des erreurs, dans les faits elles ne sont pas mieux traitées. + +Les checked exceptions sont les héritières directes de : + +- Throwable +- Exception + - IOException + +## Unchecked Exceptions + +Les unchecked exceptions n'ont pas besoin d'être explicitement `throw` ou `catch`. Elles remontent directement jusqu'à l'endroit où elles sont prises en compte. En dernier recours elles sont traitées par la JVM qui va simplement les tracer. Cependant, beaucoup de FrameWorks (dont Spring) viennent avec un outillage facilitant le traitement de ces fin exceptionnelles. + +Les unchecked exceptions sont les héritières directes de : + +- Error +- RuntimeException + - IllegalArgumentException + +## Créer votre stack d'exceptions + +- Prendre le temp d'outiller les exceptions : cela en vaut la peine ; +- Faire des exceptions spécifiques aux erreurs ; +- Ajouter une information pour connaître le type d'erreur a remonter aux utilisateurs ; +- Wrapper les exceptions "exotiques" dans des exceptions custom. diff --git a/exceptions/src/main/java/fr/ippon/exception/Firstname.java b/exceptions/src/main/java/fr/ippon/exception/Firstname.java new file mode 100644 index 00000000..848778a9 --- /dev/null +++ b/exceptions/src/main/java/fr/ippon/exception/Firstname.java @@ -0,0 +1,14 @@ +package fr.ippon.exception; + +public class Firstname { + + public Firstname(String firstname) { + assertFirstname(firstname); + } + + private void assertFirstname(String firstname) { + if (firstname == null) { + throw new MissingMandatoryValueException("firstname"); + } + } +} diff --git a/exceptions/src/main/java/fr/ippon/exception/MissingMandatoryValueException.java b/exceptions/src/main/java/fr/ippon/exception/MissingMandatoryValueException.java new file mode 100644 index 00000000..171cd6e1 --- /dev/null +++ b/exceptions/src/main/java/fr/ippon/exception/MissingMandatoryValueException.java @@ -0,0 +1,8 @@ +package fr.ippon.exception; + +public class MissingMandatoryValueException extends RuntimeException { + + public MissingMandatoryValueException(String field) { + super("Missing mandatory field: " + field); + } +} diff --git a/exceptions/src/test/java/fr/ippon/exception/ExceptionTest.java b/exceptions/src/test/java/fr/ippon/exception/ExceptionTest.java new file mode 100644 index 00000000..24e50f4c --- /dev/null +++ b/exceptions/src/test/java/fr/ippon/exception/ExceptionTest.java @@ -0,0 +1,31 @@ +package fr.ippon.exception; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import org.junit.jupiter.api.Test; + +class ExceptionTest { + + @Test + void shouldNotBuildFirstnameWithoutFirstname() { + assertThatThrownBy(() -> new Firstname(null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("firstname"); + } + + @Test + void shouldNotUpdateNullString() { + String pouet = null; + + assertThatThrownBy(() -> pouet.substring(2)).isExactlyInstanceOf(NullPointerException.class); + } + + @Test + void shouldNotReadUnknownFile() throws IOException { + assertThatThrownBy(() -> Files.readAllBytes(Paths.get("target/unknown"))).isExactlyInstanceOf(NoSuchFileException.class); + } +} -- GitLab