All Projects → specto-dev → belay

specto-dev / belay

Licence: MIT License
Robust error-handling for Kotlin and Android

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to belay

superagent-intercept
Add functions that will be called during end() e.g. for handling error conditions without having the same code all over the place.
Stars: ✭ 23 (-34.29%)
Mutual labels:  error-handling
progress-bar-log
A component to display a progress bar and last X logs at the same time.
Stars: ✭ 44 (+25.71%)
Mutual labels:  error-handling
RayS
RayS: A Ray Searching Method for Hard-label Adversarial Attack (KDD2020)
Stars: ✭ 43 (+22.86%)
Mutual labels:  robustness
cycle-confusion
Code and models for ICCV2021 paper "Robust Object Detection via Instance-Level Temporal Cycle Confusion".
Stars: ✭ 67 (+91.43%)
Mutual labels:  robustness
failure
An error handling package for Go.
Stars: ✭ 24 (-31.43%)
Mutual labels:  error-handling
perceptual-advex
Code and data for the ICLR 2021 paper "Perceptual Adversarial Robustness: Defense Against Unseen Threat Models".
Stars: ✭ 44 (+25.71%)
Mutual labels:  robustness
robust-local-lipschitz
A Closer Look at Accuracy vs. Robustness
Stars: ✭ 75 (+114.29%)
Mutual labels:  robustness
dx
JavaScript without `try...catch`.
Stars: ✭ 26 (-25.71%)
Mutual labels:  error-handling
s-attack
[CVPR 2022] S-attack library. Official implementation of two papers "Vehicle trajectory prediction works, but not everywhere" and "Are socially-aware trajectory prediction models really socially-aware?".
Stars: ✭ 51 (+45.71%)
Mutual labels:  robustness
merr
🔥 Minimal and good enough error handling library for Clojure/ClojureScript
Stars: ✭ 25 (-28.57%)
Mutual labels:  error-handling
robustness-vit
Contains code for the paper "Vision Transformers are Robust Learners" (AAAI 2022).
Stars: ✭ 78 (+122.86%)
Mutual labels:  robustness
shortcut-perspective
Figures & code from the paper "Shortcut Learning in Deep Neural Networks" (Nature Machine Intelligence 2020)
Stars: ✭ 67 (+91.43%)
Mutual labels:  robustness
miette
Fancy upgrade to std::error::Error.
Stars: ✭ 945 (+2600%)
Mutual labels:  error-handling
react-error-guard
⚛️An overlay for displaying stack frames based on create-react-app/packages/react-error-overlay
Stars: ✭ 18 (-48.57%)
Mutual labels:  error-handling
nuxt-winston-log
Nuxt module for logging SSR errors using winston
Stars: ✭ 41 (+17.14%)
Mutual labels:  error-handling
upssert
Simple REST API assertion framework
Stars: ✭ 13 (-62.86%)
Mutual labels:  assertions
Light.GuardClauses
A lightweight .NET library for expressive Guard Clauses.
Stars: ✭ 68 (+94.29%)
Mutual labels:  assertions
Robust-Semantic-Segmentation
Dynamic Divide-and-Conquer Adversarial Training for Robust Semantic Segmentation (ICCV2021)
Stars: ✭ 25 (-28.57%)
Mutual labels:  robustness
sentry-testkit
A Sentry plugin to allow Sentry report interception and further inspection of the data being sent
Stars: ✭ 78 (+122.86%)
Mutual labels:  error-handling
fejl
Error-making utility for Node apps.
Stars: ✭ 30 (-14.29%)
Mutual labels:  error-handling

Belay — Robust Error-Handling for Kotlin and Android

CI Maven Central code size Kind Speech

Code Complete: A Practical Handbook of Software Construction, on error-handling techniques:

Consumer applications tend to favor robustness to correctness. Any result whatsoever is usually better than the software shutting down. The word processor I'm using occasionally displays a fraction of a line of text at the bottom of the screen. If it detects that condition, do I want the word processor to shut down? No. I know that the next time I hit Page Up or Page Down, the screen will refresh and the display will be back to normal.

Belay is a Kotlin error-handling library which favors robustness. It serves two purposes:

  • Detect errors early during development using assertions.
  • Gracefully recover from errors when they occur in production.

Installation

In your project's build.gradle:

dependencies {
    implementation("dev.specto:belay:0.3.0")
}

Declare a top level variable to run expectations:

val expect = Expect()

It can be named anything, but for the purposes of this documentation we'll assume it's named "expect".

Next, set the top level expectation handler as early as possible during your program's initialization:

fun main() {
    expect.onGlobalFail = object : GlobalExpectationHandler() {
        override fun handleFail(exception: ExpectationException) {
            if (DEBUG) throw exception
            else log(exception.stackTraceToString())
        }
    }
    
    //
}

The global handler will be invoked when any expectations fail. Of course it's possible, and often desirable, to handle expectations individually or by subgroups, but the global handler is the ideal place to throw exceptions during development—effectively turning expectations into assertions—so that failures can be noticed and addressed immediately. In production it can be used to log all errors, regardless of how they are ultimately handled, so that they can be fixed at a later date.

⚠️ Do not call expect from the global handler, it could cause an infinite loop if that expectation fails.

Writing Expectations

The expect variable provides a host of utilities to write expectations and specify how they should be handled. They fall in 3 main categories.

Global Expectations

Global expectations only invoke the global handler when they fail.

expect(condition, optionalErrorMessage) // shorthand for expect.isTrue(…)
expect.fail("error message", optionalCause)
expect.isTrue(condition, optionalErrorMessage)
expect.isFalse(condition, optionalErrorMessage)
expect.isNotNull(value, optionalErrorMessage)
expect.isNull(value, optionalErrorMessage)
expect.isType<Type>(value, optionalErrorMessage)

Unless the global handler interrupts the program, it will proceed even when these expectations fail. Therefore, they are meant to be used when the program can proceed even when the expectations fail. For example:

fun cleanup() {
    expect.isNotNull(configuration)
    configuration = null
}

Locally-Handled Expectations

Locally-handled expectations invoke the global handler when they fail, and then a locally-defined error-handling function.

expect(condition, optionalErrorMessage) {
    // Handle the expectation failing.
    // Does not need to return or throw an exception.
}

expect.isTrue(condition, optionalErrorMessage) {
    // Handle the expectation failing.
    // Must return or throw an exception which enables smart casting.
    return
}
expect.isFalse(condition, optionalErrorMessage) { … }
val nonNullValue = expect.isNotNull(value, optionalErrorMessage) { … }
expect.isNull(value, optionalErrorMessage) { … }
val valueCastToType = expect.isType<Type>(value, optionalErrorMessage) { … }

A custom error message can be provided to all these functions.

This is great for one-off error-handling:

fun startTime(): Long {
    //
    
    expect(startTime >= 0, "startTime was negative") {
        startTime = 0
    }
    
    return startTime
}

fun animate() {
    expect.isNotNull(animator) { return }
    
    //
    animator.animate(…)
}

Expectation Blocks

Often the same error-handling strategy can be used across individual functions or blocks of code. Expectation blocks make this easy.

expect(blockHandler) {
    fail("error message", optionalCause)
    isTrue(condition, optionalErrorMessage)
    isFalse(condition, optionalErrorMessage)
    isNotNull(value, optionalErrorMessage)
    isNull(value, optionalErrorMessage)
    isType<Type>(value, optionalErrorMessage)
}

A custom error message can be provided to all these functions.

Several block handlers are offered out of the box.

Continue does nothing when an expectation fails (besides invoking the global handler):

fun stop() = expect(onFail = Continue) {
    isTrue(isActive, "time machine was not active when stop was called")
    isNotNull(configuration)
    isNotNull(controller)
    
    isActive = false
    configuration = null
    controller = null
    //
}

Return immediately returns a default value when an expectation fails:

fun startTime(): Long = expect(Return(0)) {
    //
}

ReturnLast returns the last value returned or a default if no value has been returned yet:

fun pixelColor(x: Int, y: Int): Color = expect(ReturnLast(Color.BLACK)) {
    //
}

Throw throws an exception when an expectation fails:

fun startRadiationTreatment() = expect(Throw) {
    //
}

All the provided block handlers allow an arbitrary function to be executed when an expectation fails:

expect(Return { disableController() }) {
    //
}

Block handlers which interrupt the program, like Return, ReturnLast and Throw, can also treat exceptions as failed expectations:

fun startTime(): Long = expect(Return(0), catchExceptions = true) {
    // All exceptions thrown by this function will be automatically caught
    // and handled by the expectation handler as a failed expectation.
}

It's also possible, and easy, to write your own expectation handler.

Writing Expectation Handlers

Writing custom expectation handlers is particularly useful when the same custom logic needs to be reused across a program. There are two types of expectation handlers: those that may interrupt the program when an expectation fails, and those that definitely do.

Handlers who may interrupt the program when an expectation fails, like Continue, must extend ContinueExpectationHandler. Handlers that definitely interrupt the program, for example by returning early or throwing an exception, like Return, ReturnLast or Throw, should extend ExitExpectationHandler. This distinction serves to enable smart casting for ExitExpectationHandler expectations.

You've actually already implemented a handler which uses the same interface as ContinueExpectationHandler, the global handler. The ExitExpectationHandler interface is very similar, here's an example implementation:

class DisableControllerAndReturn<T>(
    returnValue: T,
    also: ((exception: ExpectationException) -> Unit)? = null
) : ExitExpectationHandler<T>() {

    private val controller: Controller by dependencyGraph

    override fun handleFail(exception: ExpectationException): Nothing {
        controller.disable(exception.message)
        also?.invoke(exception)
        returnFromBlock(returnValue)
    }
}

Contributing

We love contributions! Check out our contributing guidelines and be sure to follow our code of conduct.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].