Programmation robuste¶
Robustesse
La robustesse d’un programme est sa capacité de gérer des erreurs et de permettre aux utilisateurs d’identifier les problèmes et de les corriger. La robustesse concerne aussi la conception du programme et non seulement l’écriture des gestionnaires d’erreurs.
throw¶
Un exemple d’utilisation de throw :
int ScoreHandler::sumScores(std::vector<int> scores) {
if(scores.empty()) {
throw std::runtime_error("Cannot sum without scores.");
}
int sum = 0;
for (int score : scores) {
sum += score;
}
return sum;
}Lever une exception
Le throw est une instruction qui permet de lever une exception. Une exécution de ce code avec un vecteur vide retournera une erreur de type std::runtime_error avec le message "Cannot sum an empty vector.".
Exemple de code avec nullptr :
std::string* TeamHandler::findTeamNameFromPlayer(std::vector<Team> teams, std::string playerName) {
int index = 0;
while (index < teams.size() && !teams[index].containsPlayer(playerName)) {
index++;
}
if (index >= teams.size()) {
return nullptr;
}
return &teams[index].getName(); // Si getName renvoie std:string&
}
void displayWinner(std::vector<int> scores) {
int highestScore = ScoreHandler::findHighestScore(scores);
if (highestScore != nullptr) {
std::string* playerName = PlayerHandler::findPlayerNameFromScore(*highestScore);
if (playerName != nullptr) {
std::string* teamName = TeamHandler::findTeamNameFromPlayer(*playerName);
if (teamName != nullptr) {
std::cout << *playerName << " from team " << *teamName << " won with score " << *highestScore << std::endl;
} else {
std::cout << "Team not found." << std::endl;
}
} else {
std::cout << "Player not found." << std::endl;
}
} else {
std::cout << "No score found." << std::endl;
}
}null, c’est nul !
null, c’est nul !Il faut éviter d’utiliser l’élément nullptr en C++ (null en Java et C#). Vous allez passer votre temps à vérifier si un objet est nullptr et cela rend le code illisible.
De plus, en C++, il faut utiliser des pointeurs partout, ce qui complique l’architecture du code.
Une solution avec throw :
std::string TeamHandler::findTeamNameFromPlayer(std::vector<Team> teams, std::string playerName) {
int index = 0;
while (index < teams.size() && !teams[index].containsPlayer(playerName)) {
index++;
}
if (index >= teams.size()) {
throw std::runtime_error("Team not found.");
}
return teams[index].getName();
}
void displayWinner(std::vector<int> scores) {
int highestScore = ScoreHandler::findHighestScore(scores);
std::string playerName = PlayerHandler::findPlayerNameFromScore(highestScore);
std::string teamName = TeamHandler::findTeamNameFromPlayer(playerName);
std::cout << playerName << " from team " << teamName << " won with score " << highestScore << std::endl;
}Il faudra attraper l’erreur plus tard et afficher le message correspondant.
try, catch¶
Exemple de try, catch :
void Application::run() {
// ...
// Application loop
while (...) {
try {
// Call to TeamHandler::findTeamNameFromPlayer
} catch (const std::runtime_error& error) {
std::cout << error.what() << std::endl; // Example output: "Team not found".
}
}
}Standard exception
Quelques exceptions utiles de la bibliothèque standard de C++:
std::exception est l’exception de base qui se dérive en :
std::logic_error:std::invalid_argumentstd::out_of_range
std::runtime_error:std::overflow_error
Consulter C++ exceptions standard pour beaucoup plus de types d’exceptions.
Soyez précis quand vous pouvez et évitez d’utiliser juste std::exception.
Ne pas gérer les erreurs imprévues !
Il est crucial de gérer les erreurs d’utilisateur, mais attraper des erreurs imprévues dans le code peut entraîner des comportements bizarres du programme et compliquer le débogage. C’est pourquoi il est important d’utiliser throw avec un catch approprié, afin de ne pas capturer des erreurs inattendues.
Les erreurs récupérables
Lever une exception n’est pas équivalent à arrêter le programme. Quand nous pouvons gérer l’erreur sans arrêter le programme, il est préférable de le faire.
Input validation¶
Un type d’erreur récupérable est des erreurs d’entrée de l’utilisateur.
Dans ce cas, nous voulons faire de la validation de l’entrée utilisateur avant de passer à l’étape suivante.
#include "input-reader.h"
#include <istream> // for std::istream
#include <sstream> // for std::istringstream
#include <string>
#include <iostream>
#include <stdexcept> // for std::invalid_argument
std::string InputReader::requestInput(const std::string &message, bool (*isValid)(std::istream&), const std::string &errorMessage) {
std::string input;
int numberAttempts = 0;
bool isInputValid = false;
// Nous limitons le nombre d'essais pour éviter les boucles infinies
while (numberAttempts < MAX_INPUT_ATTEMPTS && !isInputValid) {
// Le message est affiché pour demander à l'utilisateur de fournir une entrée
std::cout << message;
// Nous récupérons la ligne d'entrée
std::getline(std::cin, input);
// Nous validons l'entrée (en tant que stream)
std::istringstream inputStream(input);
isInputValid = isValid(inputStream);
if (!isInputValid) {
numberAttempts++;
// Nous affichons le message d'erreur qui aide l'utilisateur à fournir une entrée valide
std::cout << errorMessage << std::endl;
std::cout << MAX_INPUT_ATTEMPTS - numberAttempts << " attempts left." << std::endl;
}
}
// Si le nombre d'essais maximum est atteint
// et que l'entrée n'est toujours pas valide, nous levons une exception
if (!isInputValid) {
throw std::invalid_argument("Maximum number of attempts reached.");
}
return input;
}Exemple d’utilisation :
// Suppose a score is an integer between MIN_SCORE and MAX SCORE defined in input-reader.h
bool InputReader::isValidScore(std::istream& inputStream) {
int number;
char remainingCharacter;
bool isValidInput = !(inputStream >> number).fail();
bool isValidRange = isValidInput && number >= MIN_SCORE && number <= MAX_SCORE;
bool containsOnlyNumber = isValidInput && (inputStream >> remainingCharacter).fail();
return isValidRange && containsOnlyNumber;
}
int InputReader::requestScore() {
std::string input = requestInput("Enter the player's score: ", InputReader::isValidScore, "Please enter an integer between 0 and 100.");
return std::stoi(input); // stoi = string to integer
}Fatal errors¶
Dans main.cpp, nous pouvons attraper les erreurs fatales qui arrêtent le programme dont std::invalid_argument("Maximum number of attempts reached.") entre autre.
#include "application.h"
#include <iostream>
#include <exception>
int main() {
try {
Application application;
application.run();
} catch (const std::exception& error) {
std::cout << "Fatal error: " << error.what() << std::endl;
return 1; // Exit code 1 for failures
}
return 0;
}