diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 12434bf73616e26254f786f222bdc7b22aeb130e..9b7966391863082d46631183d92dfc693031f3a0 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 e0a0cc93d90e32a2db0ddde3303e5b089b752f04..56e2ac80fe8fac253a36fd874b02c6f1ecc5c633 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 0000000000000000000000000000000000000000..fac259392b2ea0db9fad64eca2bcf3cbccf9a049 --- /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 0000000000000000000000000000000000000000..952250791313ae9843ed35d73eb304c0a091b51c --- /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 0000000000000000000000000000000000000000..ec83e9f8b69b2c67e7929606899770bc4229788c --- /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 0000000000000000000000000000000000000000..848778a9b2a0799ac6c5b504e2b1979b4bf4e37e --- /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 0000000000000000000000000000000000000000..171cd6e1265372cb92e5e8de7b292af38f25e5a5 --- /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 0000000000000000000000000000000000000000..24e50f4c2a124706d1172126d043f085513fc852 --- /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); + } +}