\documentclass[aspectratio=169]{beamer} \usetheme{metropolis} \usepackage{lmodern} \usepackage[czech]{babel} \usepackage[utf8]{inputenc} \usepackage{graphicx} \usepackage{emoji} \setemojifont{Quivira} \usepackage{wrapfig} \usepackage{color} \usepackage{mathtools} \usepackage{hyperref} \usepackage{epstopdf} \usepackage{amsmath} \usepackage{minted} \hypersetup{ colorlinks, citecolor=black, filecolor=black, linkcolor=black, urlcolor=black } \usepackage{pdflscape} \title{Proradná obsluha chyb \emoji{dagger}} \author{Kar(t)el Kočí} \date{3.10.2020} \begin{document} \frame{\titlepage} \begin{frame}[fragile] \frametitle{Kdo za ty chyby může?} \only<2>{\huge \textbf{Uživatel!}} \only<3>{\footnotesize Programátor..} \end{frame} \begin{frame}[fragile] \frametitle{Co si pod chybami programátor představí?} \begin{itemize} \item \textbf{Nečekaný stav dat} \item Nečekaný běh aplikace (špatný algoritmus) \item Nečekané ukončení aplikace (segfault, ..) \item Nečekaný obsah souboru či čtení ze soketu \item Nečekaný obsah sdílené paměti \item \textbf{Nečekaná návratová hodnota} \item Nečekaný stav souborů (chybějící složka atd.) \item Nečekaná odezva od periferie či soketu \item A další \ldots \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{Co si pod chybami představí uživatel?} \Large Nefunguje to! \end{frame} \begin{frame}[fragile] \frametitle{Teď vážně, požadavky} \begin{columns} \column{.45\textwidth} Uživatel: \begin{itemize} \item Nechce, aby program spadl \item Když už spadne, tak chce vědět jak to opravit \item Když už neřekneme jak opravit, tak chce vědět proč \item Nechce více informací než dokáže vstřebat \end{itemize} \column{.45\textwidth} Programátor: \begin{itemize} \item To někdy není možné a je to jediné východisko \item Programátor nemůže pokrýt každou situaci \item To nemusí být ani v moci programu \item Potřebuje trace, například aby dohledal jak se to stalo \end{itemize} \end{columns} Programátor není Bůh, ale měl by být a měl by se snažit uživatelům práci s jeho stvořením zpříjemnit. \end{frame} \begin{frame}[fragile] \frametitle{Co s chybou když nastane?} Záleží na tom o jakou chybu se jedná. \begin{itemize} \item Fatální chyby \begin{itemize} \item Pády (\textit{segfault, invalid instruction, \ldots}) \item Nečekané stavy (\textit{tady ještě před chvílí byl soubor \ldots}) \item Bezvýchodné stavy (\textit{tady měl být soubor se vstupem \dots}) \end{itemize} \item Řešitelné chyby \begin{itemize} \item Krajní (\textit{soubor vytvořil někdo jiný \ldots}) \item Očekávané (\textit{soubor se nezdařilo vytvořit, protože chybí složka, vytvoříme složku \dots}) \item Ignorované (\textit{unlink souboru selhal, protože neexistuje \dots}) \end{itemize} \end{itemize} A taky záleží, jestli jsme knihovna nebo program! \end{frame} \begin{frame}[fragile] \frametitle{Co když nastane pád?} \begin{columns} \column{.50\textwidth} \centerline{\textbf{Program}} Pokud se nesnaží, tak se ani o tom nedozví. \column{.50\textwidth} \centerline{\textbf{Knihovna}} Ani se o tom nedozví a většinou by se neměla ani snažit. \end{columns} \vspace{1cm} \begin{minted}[frame=lines]{c} struct foo *foo = NULL; foo->fee = 0; \end{minted} \end{frame} \begin{frame}[fragile] \frametitle{Co když nastane nečekaný stav?} \begin{columns} \column{.50\textwidth} \centerline{\textbf{Program}} Ukončení programu a výpis stavových informací \begin{minted}[frame=lines]{python} file = open(path, "w") file.close() file = open(path) # raises: FileNotFoundError \end{minted} \begin{minted}[frame=lines]{c} fclose(file); file = fopen(path, "r"); assert(file); \end{minted} \column{.50\textwidth} \centerline{\textbf{Knihovna}} Necháme vyřešit aplikaci. Nikdy ne \texttt{exit} a vyvarujte se \texttt{abort}. \begin{minted}[frame=lines]{c} fclose(file); file = fopen(path, "r"); if (file == NULL) { liberrno = LIB_ERR_LOST_FILE; return false; } \end{minted} \end{columns} \end{frame} \begin{frame}[fragile] \frametitle{Odbočka k C a knihovnám: Jak hlásit error?} \begin{itemize} \item Návratová hodnota funkce \begin{minted}[frame=lines]{c} enum { FOO_ERR_FOO, } foo() { return FOO_ERR_FOO; } \end{minted} \item Proměnná s chybou \begin{minted}[frame=lines]{c} enum liberror { LIB_ERR_FOO, }; thread_local enum liberror liberrno; \end{minted} \item Výjimka (s nějakou knihovnou, která je implementuje) \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{Co když nastane bezvýchodný stav?} \begin{columns} \column{.50\textwidth} \centerline{\textbf{Program}} Ukončení programu. \begin{minted}[frame=lines]{python} try: file = open(sys.argv[1]) except FileNotFoundError: logger.critical("Input missing: %s", sys.argv[1]) except PermissionError: logger.critical( "Input unaccessible: %s", sys.argv[1]) \end{minted} \column{.50\textwidth} \centerline{\textbf{Knihovna}} Necháme vyřešit aplikaci. \begin{minted}[frame=lines]{python} def handle_args(argv): """ :raises FileNotFoundError: First argument wasn't valid path :raises PermissionError: First is unaccessible path """ file = open(argv[1]) \end{minted} \end{columns} \end{frame} \begin{frame}[fragile] \frametitle{Odbočka k ukončovacím rutinám: Jak ukončit program?} Je nutné uvolnit každou dosažitelnou paměť? Rozdělme si zdroje vůči procesu:\\ \begin{columns} \column{.50\textwidth} Interní \begin{itemize} \item Alokovaná paměť \item Otevřené file descriptory (soubory, sockety, atd.) \item Namapovaná paměť \end{itemize} \column{.50\textwidth} Externí \begin{itemize} \item Data buffery s výstupem na disk (ne \texttt{fclose} ale \texttt{fflush}) \item Dočasné soubory dosažitelné na file-systému \end{itemize} \end{columns} \vspace{0.4cm} Dělejme jen to, co musíme. Zbytek za nás udělá kernel a jazyk. Interní za nás udělá někdo jiný, my se musíme starat pouze o externí. \end{frame} \begin{frame}[fragile] \frametitle{Ještě větší odbočka k C \texttt{exit}} \url{https://www.gnu.org/software/libc/manual/html_node/Termination-Internals.html} \begin{minted}[frame=lines]{c} typedef void (*onexit_t)(void *arg); struct onexit { onexit_t func; void *arg; struct onexit *next; }; struct onexit *onexit = NULL; void onexit_handler() { for (; onexit; onexit = onexit->next) onexit->func(onexit->arg); } atexit(onexit_handler); \end{minted} \end{frame} \begin{frame}[fragile] \frametitle{Co když nastane řešitelná chyba?} \begin{columns} \column{.50\textwidth} \centerline{\textbf{Program}} Tak ji prostě vyřešíme, nebo ještě lépe ji předejdeme. \begin{minted}[frame=lines]{python} pathlib.Path("foo").mkdir( parents=True, exist_ok=True) file = open("foo/fee", "w") \end{minted} \column{.50\textwidth} \centerline{\textbf{Knihovna}} Tak ji vyřešíme. Lepší je ale nechat vybrat aplikaci, jestli se má automaticky řešit nebo vrátit raději error. \begin{minted}[frame=lines]{python} def open(self, path, mkdir=True): if mkdir: pathlib.Path(path).parent.mkdir( parent=True, exist_ok=True) file = open(path, "w") \end{minted} \end{columns} \end{frame} \begin{frame}[fragile] \frametitle{Závěrem: jde to i přehnat} \begin{minted}[frame=lines]{c} int fd = open(path, O_RDONLY); char buf[BUFSIZ]; while (true) { if (read(fd, buf, BUFSIZ) == -1) { switch (errno) { case EWOULDBLOCK: case EAGAIN: continue; \end{minted} \end{frame} \begin{frame}[fragile] \Large Error: unable to exit, user is not informed! \end{frame} \begin{frame}[fragile] \frametitle{Nahlášení chyby} Před tím než chybu ošetříme, musíme ji nahlásit! V pořadí od vývojářsky po uživatelsky přivětivé: \begin{itemize} \item Memory dump \item Stack trace \item Popis chyby se zdrojem chyby (zdrojový soubor, funkce, řádka, \dots) \item Popis chyby \item Popis chyby a návrh řešení \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{Co kdy zvolit ale?} \begin{description} \item[Memory dump] jen pro pády a většinou zajistí systém \item[Stack trace] jen pro nečekané stavy a na vyžádání \item[Zdroj chyby] jen jako méně invazivní alternativa ke stacktrace \item[Popis] vždy \item[Návrh řešení] vždy pokud je možné \end{description} \end{frame} \begin{frame}[fragile] \frametitle{Co v popisu říci uživateli, aby jim i nám to k něčemu bylo?} \begin{enumerate} \item Popis by měl být pokud možno unikátní pro každou chybu. \item Poskytnout výpis relevantních dat (cesta k souboru a pod.). \item Nevypisujte nerelevantní data! \item Popis by měl být výstižný a přesný. \end{enumerate} Ujistěte se, že data která vypisujete jsou validní! \begin{verbatim} Called uri_path on URI of scheme: https \end{verbatim} \begin{minted}[frame=lines]{diff} -error("URI download failed (" .. u:path() .. "): " .. u:download_error()) +error("Getting URI (" .. u:uri() .. ") failed: " .. u:download_error()) \end{minted} \end{frame} \begin{frame}[fragile] \frametitle{Kdy hlásit chybu?} \textbf{Vždy!} Hlašte chyby ve více úrovních: \begin{description} \item[CRITICAL] pro nečekané a bezvýchodné stavy (následuje ukončení programu) \item[ERROR] pro krajní řešitelné chyby \item[WARNING] pro krajní řešitelné chyby \item[NOTICE] žádné chyby \item[INFO] pro očekávané chyby \item[DEBUG] pro ignorované chyby \end{description} \end{frame} \begin{frame}[fragile] \frametitle{Checklist závěrem} \begin{enumerate} \item Zamyslete se, co za data od volání získáte, a jak moc datům ke kterým přistupujete můžete věřit \item Chyby které nám mohou nastat rozřadíme \item Při detekci chyby oznámíme uživateli \item Vyřešíme chybu dle toho o jaký typ se jedná \end{enumerate} \end{frame} \begin{frame} \Large \textbf{Fatal error: no more slides \emoji{cry}} \Large Děkuji za pozornost. \url{git.cynerd.cz} \end{frame} \end{document}