timebombs


Nametimebombs JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://github.com/mattmezza/timebombs
SummaryA small package to keep tech debt to a minimum.
upload_time2024-05-24 16:46:53
maintainerNone
docs_urlNone
authorMatteo Merola
requires_python<4.0,>=3.9
licenseNone
keywords ci continuous integration tech debt
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![banner](https://raw.githubusercontent.com/mattmezza/timebombs/master/docs/timebomb.png)

timebombs
===

> Stay clean, move fast.

## intro

Have you ever been in a situation where you found yourself leaving comments in
the code saying something like this:

```
# TODO - remove this after ...
# TODO - this needs cleaning
```

Well, who hasn't!? We all have good intentions to actually go back and do the
removal, do the cleaning etc... But then we:

- forget
- get swamped with other prios
- overtaken by the NMFP (not my f- problem) attitude
- etc.

and these comments end up staying there forever.

## proposal

Wouldn't it be nice to have a mechanism to force teams to actually give a shit?

Let me introduce the concept of a time bomb...

A timebomb is a conscious decision to tackle a certain action at a later
moment in time. It has a detonator that is programmed to explode at some point
in time.

It can stay there, in the code, unarmed and do nothing other than representing
the same thing a comment would have.

When the detonation moment approaches, the time bomb arms itself and you can
make it do things via callback functions. A classic action you might want to
take is to trigger a log message with `WARN` level saying something like:

```
WARN: timebomb PRJ-123 is about to explode in 30 days
```

The above could trigger notification on your alerting system such that work is
properly prioritized and done. At this point, you can either:

- do the work that was post-poned at a later point in time
- or defuse the bomb and post-pone it in the future

> But hey, what's the incentive of actually doing this? What if nothing gets
done anyways and the bomb passes the detonation moment?

Well, if you do nothing, the bomb might actually explode and when that happens
you can make it do something (always via the same mechanism - callback fun).

> What do you mean with *might* explode?

You would be surprised to realize how sometimes old code is
actually dead code. In that case, the time bomb will stay there, inert,
untouched, unharmful (like them WW2 unexploded bombs that people keep finding
here and there throughout Europe).

A typical example of an explosion callback would be emitting a log line, only
this time with an `ERR` log level saying something similar to the above.

This would (or at least *should*) result in some serious alerting going on,
ensuring that:

- the right attention is given to the resolution of the timebomb (i.e. doing
the cleaning, doing the removal, etc.)
- a clear team decision is take to post-pone the timebomb again (making it
visible that debt is being incurred)

For extreme teams, folks who like to live on the edge, upon explosion, a time
bomb could be configured to panic or throw an exception. This definitely makes
things a tag *more* interesting and *encourages* teams to pay back technical
debt.


## show me the code

> All well and swell, but what would it look like to adopt timebombs???

Imagine you have moved on to a `v2` version of your `APIs` and you want to
make sure one day you do go back and clean `v1`. Here's what the hypothetical
code would look like.

```python
# timebombs.py
import functools
import logging
import timebombs


log = logging.getLogger(__name__)
on_armed = lambda tb: log.warning("%s is exploding soon, take action!", tb)
on_exploded = lambda tb: log.error("BOOM! %s exploded!", tb)
timebomb = functools.partial(
    timebombs.timebomb, on_armed=on_armed, on_exploded=on_exploded
)

DEPRECATE_V1_ENDPOINTS = timebomb(
    "JIRA-123",
    "2025-05-22",
    "Endpoints for v1 should be removed by this time.",
)
```

```python
# endpoint.py
from . import timebombs

@get("/v2/resource")
async def get_resource_v2(req, res):
    ...


@get("/v1/resource")
async def get_resource_v1(req, res):
    timebombs.DEPRECATE_V1_ENDPOINTS()
    ...
```

In this example, all timebombs are collected in a single module and imported
where necessary. This is done to have a visual easy way to understand what is
the level of tech debt a team is facing. When the `timebombs.py` module is
getting too big, something's fishy and your life should be miserable anyways.

## i'm not convinced

> This is too much work, I'm not convinced it is more useful than just leaving
comments here and there...

Ok, fair point. What if I told you that you can monitor the amount of unarmed,
armed and exploded timebombs at *ci* time?

In the following example, note the presence of a `timebombs.Registry` to
collect and accumulate all the timebombs you plant around.

```python
# timebombs.py
import functools
import logging
import timebombs


log = logging.getLogger(__name__)
on_armed = lambda tb: log.warning("%s is exploding soon, take action!", tb)
on_exploded = lambda tb: log.error("BOOM! %s exploded!", tb)
reg = timebombs.Registry()
timebomb = functools.partial(
    timebombs.timebomb,
    on_armed=on_armed,
    on_exploded=on_exploded,
    registry=reg,
)

DEPRECATE_V1_ENDPOINTS = timebomb(
    "JIRA-123",
    "2025-05-22",
    "Endpoints for v1 should be removed by this time.",
)
```

Then, somewhere in your *ci* (or from your *cli*):

```bash
$ python -m timebombs "timebombs:reg" --max-armed 20
```

The above would fail with exit status code equal to the number of armed time
bombs (if above `20`) or succeed with exit code `0` otherwise.

It basically asks the question: "As of now, do I have more than `20` armed
timebombs that will explode soon? If so, how many?"

> What if you wanted to see how many time bombs would explode in a month from
now?

```bash
$ python -m timebombs "timebombs:reg" \
    --skip-armed \
    --at-time "$(date -d "$(date +%Y-%m-%d) 1 month" +%Y-%m-%d)"
```

It answers the question: "As of a month from now, will I have any explosion of
a timebomb? If so, how many?"

With this step added to your *ci*, tech debt is actually popping up way
earlier and can be addressed on time.

## summing up

Tech debt is not a problem per se, but **unmanaged** technical debt is.

Timebombs offer *a* possible way to manage it and keep it under control. This
technique offers a way to look into the future (maybe during a cycle of
planning) and answer the question:

> How much time should we reserve for technical debt during the next cycle?

The point is not to avoid debt at any cost, sometimes you just can't. The
whole point is to be honest with yourself.

If, *as a team* you've agreed you were going to pay back technical debt by a
certain time, you should actually do it, right?

Of course, it's going to be difficult to forecast when you will have time to
pay back tech debt. However, the only fact that you actually have to do the
following:

- come up with a point in time by when you want to pay back the debt (forces
you to think of a reasonable time)
- make a *pr* that someone else will review (makes it visible to the rest of
the team - everybody should agree to acquire debt)
- make a conscious decision *as a team* to either pay back the debt (if you
can afford it) or renegotiate it (if you can't)

it pushes teams to spend more time reflecting on these topics, which is good
anyways.

Lastly, there are no guidelines here in terms of what amount of timebombs is
acceptable or not. This is (and will always be) up to you.

### how to get started

```bash
pip install timebombs
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/mattmezza/timebombs",
    "name": "timebombs",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": "CI, continuous integration, tech debt",
    "author": "Matteo Merola",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/44/c4/53cc1df0219622ffaeae6d8ae98b5dc59fe55ff54ef5f12abd1f28da4556/timebombs-0.1.0.tar.gz",
    "platform": null,
    "description": "![banner](https://raw.githubusercontent.com/mattmezza/timebombs/master/docs/timebomb.png)\n\ntimebombs\n===\n\n> Stay clean, move fast.\n\n## intro\n\nHave you ever been in a situation where you found yourself leaving comments in\nthe code saying something like this:\n\n```\n# TODO - remove this after ...\n# TODO - this needs cleaning\n```\n\nWell, who hasn't!? We all have good intentions to actually go back and do the\nremoval, do the cleaning etc... But then we:\n\n- forget\n- get swamped with other prios\n- overtaken by the NMFP (not my f- problem) attitude\n- etc.\n\nand these comments end up staying there forever.\n\n## proposal\n\nWouldn't it be nice to have a mechanism to force teams to actually give a shit?\n\nLet me introduce the concept of a time bomb...\n\nA timebomb is a conscious decision to tackle a certain action at a later\nmoment in time. It has a detonator that is programmed to explode at some point\nin time.\n\nIt can stay there, in the code, unarmed and do nothing other than representing\nthe same thing a comment would have.\n\nWhen the detonation moment approaches, the time bomb arms itself and you can\nmake it do things via callback functions. A classic action you might want to\ntake is to trigger a log message with `WARN` level saying something like:\n\n```\nWARN: timebomb PRJ-123 is about to explode in 30 days\n```\n\nThe above could trigger notification on your alerting system such that work is\nproperly prioritized and done. At this point, you can either:\n\n- do the work that was post-poned at a later point in time\n- or defuse the bomb and post-pone it in the future\n\n> But hey, what's the incentive of actually doing this? What if nothing gets\ndone anyways and the bomb passes the detonation moment?\n\nWell, if you do nothing, the bomb might actually explode and when that happens\nyou can make it do something (always via the same mechanism - callback fun).\n\n> What do you mean with *might* explode?\n\nYou would be surprised to realize how sometimes old code is\nactually dead code. In that case, the time bomb will stay there, inert,\nuntouched, unharmful (like them WW2 unexploded bombs that people keep finding\nhere and there throughout Europe).\n\nA typical example of an explosion callback would be emitting a log line, only\nthis time with an `ERR` log level saying something similar to the above.\n\nThis would (or at least *should*) result in some serious alerting going on,\nensuring that:\n\n- the right attention is given to the resolution of the timebomb (i.e. doing\nthe cleaning, doing the removal, etc.)\n- a clear team decision is take to post-pone the timebomb again (making it\nvisible that debt is being incurred)\n\nFor extreme teams, folks who like to live on the edge, upon explosion, a time\nbomb could be configured to panic or throw an exception. This definitely makes\nthings a tag *more* interesting and *encourages* teams to pay back technical\ndebt.\n\n\n## show me the code\n\n> All well and swell, but what would it look like to adopt timebombs???\n\nImagine you have moved on to a `v2` version of your `APIs` and you want to\nmake sure one day you do go back and clean `v1`. Here's what the hypothetical\ncode would look like.\n\n```python\n# timebombs.py\nimport functools\nimport logging\nimport timebombs\n\n\nlog = logging.getLogger(__name__)\non_armed = lambda tb: log.warning(\"%s is exploding soon, take action!\", tb)\non_exploded = lambda tb: log.error(\"BOOM! %s exploded!\", tb)\ntimebomb = functools.partial(\n    timebombs.timebomb, on_armed=on_armed, on_exploded=on_exploded\n)\n\nDEPRECATE_V1_ENDPOINTS = timebomb(\n    \"JIRA-123\",\n    \"2025-05-22\",\n    \"Endpoints for v1 should be removed by this time.\",\n)\n```\n\n```python\n# endpoint.py\nfrom . import timebombs\n\n@get(\"/v2/resource\")\nasync def get_resource_v2(req, res):\n    ...\n\n\n@get(\"/v1/resource\")\nasync def get_resource_v1(req, res):\n    timebombs.DEPRECATE_V1_ENDPOINTS()\n    ...\n```\n\nIn this example, all timebombs are collected in a single module and imported\nwhere necessary. This is done to have a visual easy way to understand what is\nthe level of tech debt a team is facing. When the `timebombs.py` module is\ngetting too big, something's fishy and your life should be miserable anyways.\n\n## i'm not convinced\n\n> This is too much work, I'm not convinced it is more useful than just leaving\ncomments here and there...\n\nOk, fair point. What if I told you that you can monitor the amount of unarmed,\narmed and exploded timebombs at *ci* time?\n\nIn the following example, note the presence of a `timebombs.Registry` to\ncollect and accumulate all the timebombs you plant around.\n\n```python\n# timebombs.py\nimport functools\nimport logging\nimport timebombs\n\n\nlog = logging.getLogger(__name__)\non_armed = lambda tb: log.warning(\"%s is exploding soon, take action!\", tb)\non_exploded = lambda tb: log.error(\"BOOM! %s exploded!\", tb)\nreg = timebombs.Registry()\ntimebomb = functools.partial(\n    timebombs.timebomb,\n    on_armed=on_armed,\n    on_exploded=on_exploded,\n    registry=reg,\n)\n\nDEPRECATE_V1_ENDPOINTS = timebomb(\n    \"JIRA-123\",\n    \"2025-05-22\",\n    \"Endpoints for v1 should be removed by this time.\",\n)\n```\n\nThen, somewhere in your *ci* (or from your *cli*):\n\n```bash\n$ python -m timebombs \"timebombs:reg\" --max-armed 20\n```\n\nThe above would fail with exit status code equal to the number of armed time\nbombs (if above `20`) or succeed with exit code `0` otherwise.\n\nIt basically asks the question: \"As of now, do I have more than `20` armed\ntimebombs that will explode soon? If so, how many?\"\n\n> What if you wanted to see how many time bombs would explode in a month from\nnow?\n\n```bash\n$ python -m timebombs \"timebombs:reg\" \\\n    --skip-armed \\\n    --at-time \"$(date -d \"$(date +%Y-%m-%d) 1 month\" +%Y-%m-%d)\"\n```\n\nIt answers the question: \"As of a month from now, will I have any explosion of\na timebomb? If so, how many?\"\n\nWith this step added to your *ci*, tech debt is actually popping up way\nearlier and can be addressed on time.\n\n## summing up\n\nTech debt is not a problem per se, but **unmanaged** technical debt is.\n\nTimebombs offer *a* possible way to manage it and keep it under control. This\ntechnique offers a way to look into the future (maybe during a cycle of\nplanning) and answer the question:\n\n> How much time should we reserve for technical debt during the next cycle?\n\nThe point is not to avoid debt at any cost, sometimes you just can't. The\nwhole point is to be honest with yourself.\n\nIf, *as a team* you've agreed you were going to pay back technical debt by a\ncertain time, you should actually do it, right?\n\nOf course, it's going to be difficult to forecast when you will have time to\npay back tech debt. However, the only fact that you actually have to do the\nfollowing:\n\n- come up with a point in time by when you want to pay back the debt (forces\nyou to think of a reasonable time)\n- make a *pr* that someone else will review (makes it visible to the rest of\nthe team - everybody should agree to acquire debt)\n- make a conscious decision *as a team* to either pay back the debt (if you\ncan afford it) or renegotiate it (if you can't)\n\nit pushes teams to spend more time reflecting on these topics, which is good\nanyways.\n\nLastly, there are no guidelines here in terms of what amount of timebombs is\nacceptable or not. This is (and will always be) up to you.\n\n### how to get started\n\n```bash\npip install timebombs\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A small package to keep tech debt to a minimum.",
    "version": "0.1.0",
    "project_urls": {
        "Documentation": "https://matteo.merola.co/timebombs",
        "Homepage": "https://github.com/mattmezza/timebombs",
        "Repository": "https://github.com/mattmezza/timebombs"
    },
    "split_keywords": [
        "ci",
        " continuous integration",
        " tech debt"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3382a4d165b6c94131083708285d965cbc90c4ff44c8bff3eb936310a93c9b23",
                "md5": "368245b11a54046a131a5013ad645f38",
                "sha256": "7c8129ee0079f049f03523da560306a8875e92ae413b8b89dd29fc8a99416063"
            },
            "downloads": -1,
            "filename": "timebombs-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "368245b11a54046a131a5013ad645f38",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 9980,
            "upload_time": "2024-05-24T16:46:51",
            "upload_time_iso_8601": "2024-05-24T16:46:51.380001Z",
            "url": "https://files.pythonhosted.org/packages/33/82/a4d165b6c94131083708285d965cbc90c4ff44c8bff3eb936310a93c9b23/timebombs-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "44c453cc1df0219622ffaeae6d8ae98b5dc59fe55ff54ef5f12abd1f28da4556",
                "md5": "ece5432b6efa525fe6ba76cd3f255a18",
                "sha256": "1f8569a3a4f9a7e903b4c2e731bd7000e74f26ffd134dce345da8200aad3b719"
            },
            "downloads": -1,
            "filename": "timebombs-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "ece5432b6efa525fe6ba76cd3f255a18",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 6707,
            "upload_time": "2024-05-24T16:46:53",
            "upload_time_iso_8601": "2024-05-24T16:46:53.372038Z",
            "url": "https://files.pythonhosted.org/packages/44/c4/53cc1df0219622ffaeae6d8ae98b5dc59fe55ff54ef5f12abd1f28da4556/timebombs-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-24 16:46:53",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "mattmezza",
    "github_project": "timebombs",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "timebombs"
}
        
Elapsed time: 0.22668s