All Projects → spoptchev → Scientist

spoptchev / Scientist

Licence: mit
A kotlin library for refactoring code. Port of GitHub's scientist.

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to Scientist

Jedi
Awesome autocompletion, static analysis and refactoring library for python
Stars: ✭ 5,037 (+8295%)
Mutual labels:  refactoring
Experiment
🔬 Elixir Library for carefully refactoring critical paths by performing experiments.
Stars: ✭ 6 (-90%)
Mutual labels:  refactoring
Refactoring Aware Diff
RAID is a tool pipeline that seamlessly enriches GitHub diff results with refactoring information.
Stars: ✭ 50 (-16.67%)
Mutual labels:  refactoring
Phpactor
PHP completion, refactoring, introspection tool and language server.
Stars: ✭ 591 (+885%)
Mutual labels:  refactoring
Scientist
🔬 A Ruby library for carefully refactoring critical paths.
Stars: ✭ 6,301 (+10401.67%)
Mutual labels:  refactoring
Lispy
Short and sweet LISP editing
Stars: ✭ 856 (+1326.67%)
Mutual labels:  refactoring
Rainbow levels.vim
A different approach to code highlighting.
Stars: ✭ 415 (+591.67%)
Mutual labels:  refactoring
Cxxctp
DEPRECATED. USE INSTEAD github.com/blockspacer/flextool
Stars: ✭ 58 (-3.33%)
Mutual labels:  refactoring
Wild Workouts Go Ddd Example
Complete application to show how to apply DDD, Clean Architecture, and CQRS by practical refactoring of a Go project.
Stars: ✭ 756 (+1160%)
Mutual labels:  refactoring
Clink
A developer assistance tool to help with refactoring and keeping related code up to date with changes
Stars: ✭ 40 (-33.33%)
Mutual labels:  refactoring
Scalafix
Refactoring and linting tool for Scala
Stars: ✭ 597 (+895%)
Mutual labels:  refactoring
Pre Commit
A framework for managing and maintaining multi-language pre-commit hooks.
Stars: ✭ 7,024 (+11606.67%)
Mutual labels:  refactoring
Honeyjs
An open source Javascript Honey Pot implementation
Stars: ✭ 20 (-66.67%)
Mutual labels:  refactoring
Coca
Coca is a toolbox which is design for legacy system refactoring and analysis, includes call graph, concept analysis, api tree, design patterns suggest. Coca 是一个用于系统重构、系统迁移和系统分析的瑞士军刀。它可以分析代码中的测试坏味道、模块化分析、行数统计、分析调用与依赖、Git 分析以及自动化重构等。
Stars: ✭ 576 (+860%)
Mutual labels:  refactoring
Experiment
A Go package for experimenting with and evaluating new code paths.
Stars: ✭ 51 (-15%)
Mutual labels:  refactoring
3rs Of Software Architecture
A guide on how to write readable, reusable, and refactorable software
Stars: ✭ 525 (+775%)
Mutual labels:  refactoring
Programming Book Recommendations List
My personal list of books that I recommend to read if you are a software developer
Stars: ✭ 22 (-63.33%)
Mutual labels:  refactoring
Dicas De Programacao Em Ruby
Dicas para iniciantes de boas práticas de desenvolvimento de software em Ruby
Stars: ✭ 59 (-1.67%)
Mutual labels:  refactoring
Refactoring2 Zh
《重构 改善既有代码的设计第二版》中文翻译
Stars: ✭ 54 (-10%)
Mutual labels:  refactoring
Comby
A tool for structural code search and replace that supports ~every language.
Stars: ✭ 912 (+1420%)
Mutual labels:  refactoring

Scientist Build Status

A kotlin library for carefully refactoring critical paths in your application.

This library is inspired by the ruby gem scientist.

How do I science?

Let's pretend you're changing the way you handle permissions in a large web app. Tests can help guide your refactoring, but you really want to compare the current and refactored behaviors under load.

fun isAllowed(user: User): Boolean = scientist<Boolean, Unit>() conduct {
    experiment { "widget-permissions" }
    control { user.isAllowedOldWay() }
    candidate { user.isAllowedNewWay() }
}

Wrap a control lambda around the code's original behavior, and wrap candidate around the new behavior. When conducting the experiment conduct will always return whatever the control lambda returns, but it does a bunch of stuff behind the scenes:

  • It decides whether or not to run the candidate lambda,
  • Randomizes the order in which control and candidate lambdas are run,
  • Measures the durations of all behaviors,
  • Compares the result of candidate to the result of control,
  • Swallows (but records) any exceptions thrown in the candidate lambda, and
  • Publishes all this information.

Scientist and Experiment

Compared to other scientist libraries this library separates the concepts of a scientist and the experiment. Which in turn gives you more freedom and flexibility to compose and reuse scientists and experiments (especially with dependency injection frameworks).

val scientist = scientist<Boolean, Unit>()
val experiment = experiment<Boolean, Unit>() {
    control { true }
}

val result = scientist conduct experiment

Setting up the scientist

The scientist is responsible for setting up the environment of an experiment and conducting it.

Publishing results

The examples above will run, but they're not really doing anything. The candidate lambdas run every time and none of the results get published. Add a publisher to control the result reporting:

val scientist = scientist<Boolean, Unit> {
    publisher { result -> logger.info(result) }
}

You can also extend the publisher typealias which then can be used as a parameter of the publisher lambda:

val logger = loggerFactory.call()

class LoggingPublisher(val logger: Logger) : Publisher<Boolean, Unit> {
    override fun invoke(result: Result<Boolean, Unit>) {
        logger.info(result)
    }
}

val loggingPublisher = LoggingPublisher(logger)

val scientist = scientist<Boolean, Unit> {
    publisher(loggingPublisher)
}

Controlling matches

Scientist compares if control and candidate values have matched by using ==. To override this behavior, use match to define how to compare observed values instead:

val scientist = scientist<Boolean, Unit> {
    match { candidate, control -> candidate != control }
}

candidate and control are both of type Outcome (a sealed class) which either can be Success or Failure. As an example take a look at the default implementation:

class DefaultMatcher<in T> : Matcher<T> {
    override fun invoke(candidate: Outcome<T>, control: Outcome<T>): Boolean = when(candidate) {
        is Success -> when(control) {
            is Success -> candidate.value == control.value
            is Failure -> false
        }
        is Failure -> when(control) {
            is Success -> false
            is Failure -> candidate.errorMessage == control.errorMessage
        }
    }
}

A Success outcome contains the value that has been evaluated. A Failure outcome contains the exception that was caught while evaluating a control or candidate statement.

Adding context

To provide additional data to the scientist Result and Experiments you can use the context lambda to add a context provider:

val scientist = scientist<Boolean, Map<String, Boolean>> {
    context { mapOf("yes" to true, "no" to false) }
}

The context is evaluated lazily and is exposed to the publishable Result by evaluating val context = result.contextProvider() and in the experiments conductibleIf lambda that will be described further down the page.

Ignoring mismatches

During the early stages of an experiment, it's possible that some of your code will always generate a mismatch for reasons you know and understand but haven't yet fixed. Instead of these known cases always showing up as mismatches in your metrics or analysis, you can tell the scientist whether or not to ignore a mismatch using the ignore lambda. You may include more than one lambda if needed:

val scientist = scientist<Boolean, Map<String, Boolean>> {
    ignore { candidate, control -> candidate.isFailure() }
}

Like in match candidate and control are of type Outcome.

Testing

When running your test suite, it's helpful to know that the experimental results always match. To help with testing, Scientist defines a throwOnMismatches field. Only do this in your test suite!

To throw on mismatches:

val scientist = scientist<Boolean, Map<String, Boolean>> {
    throwOnMismatches { true }
}

Scientist will throw a MismatchException exception if any observations don't match.

Setting up an experiment

With an experiment you are setting up tests for the critical paths of your application by specifying a control and candidate lambda.

fun experiment = experiment<Boolean, Unit> {
    name { "widget-permissions" }
    control { user.isAllowedOldWay() }
    candidate { user.isAllowedNewWay() }
}

Enabling/disabling experiments

Sometimes you don't want an experiment to run. Say, disabling a new code path for anyone who isn't member. You can disable an experiment by setting a conductibleIf lambda. If this returns false, the experiment will merely return the control value.

experiment<Boolean, Unit> {
    // ...
    conductibleIf { user.isMember() }
}

The conductibleIf lambda can also take a contextProvider as a parameter:

experiment<Boolean, Map<String, Boolean>> {
    // ...
    conductibleIf { context -> context()["externalCondition"]!! }
}

Handling errors

Scientist catches and tracks all exceptions thrown in a control or candidate lambda. To catch a more restrictive set of exceptions add a catches lambda to your experiment setup:

experiment<Boolean, Unit> {
    // ...
    catches { e -> e is NullPointerException }
}

This tells the scientist to catch NullPointerException and throw any other exception when running the control and candidate lambdas.

Complete DSL example

You can compose and execute experiments by putting conduct between scientist and an experiment context:

val result = scientist<Double, Unit> {
    publisher { result -> println(result) }
} conduct { // or conduct experiment {
    experiment { "experiment-dsl" }
    control { Math.random() }
    candidate { Math.random() }
}

Java interop

The Java interoperability can certainly be improved but should be sufficient for now:

public boolean isAllowed(User user) {
    Scientist<Boolean, String> scientist = Setup.scientist(setup -> setup
            .context(() -> "execute")
    );

    Experiment<Boolean, String> experiment = Setup.experiment(setup -> setup
            .name(() -> "experiment-name")
            .control("test-control", () -> user.isAllowedOldWay())
            .candidate("test-candidate", () -> user.isAllowedNewWay())
            .conductibleIf((contextProvider) -> contextProvider.invoke().equals("execute"))
    );

    return scientist.evaluate(experiment);
}

Installation

Maven:

<dependency>
  <groupId>com.github.spoptchev</groupId>
  <artifactId>scientist</artifactId>
  <version>1.0.2</version>
</dependency>

Gradle:

compile 'com.github.spoptchev:scientist:1.0.2'
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].