Piranha: An Open Source Tool to Automatically Delete Stale Code
eng.uber.com
eng.uber.com
We now have monitoring of success-rate-per-endpoint, so even if 99.9% of requests are successful, if a single function crashes 100% of the time we should still notice :)
Most of the time this would happen, the difference between "95% of people can't see a posting" and "100% of people can't see a posting" would be pretty small, but I guess if the marketplace is very liquid the difference might actually be substantial. But unless you're uber or someplace like that, where there's a big difference between "95% of drivers can't see a ride request (so it takes slightly longer to find a driver)" and "100% of drivers can't see a ride request (so no driver is ever found)", I can't imagine too many cases where the difference is likely to make a practical difference.
https://en.wikipedia.org/wiki/Red_garra#Relationship_with_hu...
One of the best ways to start is add a simple kill-switch to low-impact code. (More about that in the book.) But, I have to ask: what are the main issues that are blocking you from starting? Is it that you need ideas for cases, or are worried about breaking something, or just want to ensure best practices from the start?
We're constantly writing new guides, both as blog posts[2] and long-lived guide articles on our documentation site[3]. Feel free to message me on Twitter[4] if you need more help!
[1] https://launchdarkly.com/effective-feature-management-ebook/
[2] https://blog.launchdarkly.com/
That still saves the time of: a) remembering that the flag is stale and must be removed, b) actually removing the code and putting the diff up. Piranha diffs for a single flag are also generally small enough to fully read them during code review.
We do have comprehensive unit tests for both the Piranha tool and the target codebase(s), and the majority (65%) of diffs generated by Piranha are landed without changes (and the most common change in those that are changed is to delete extra lines that Piranha couldn't prove as stale). Thus far we haven't had an outage caused by a Piranha deletion, but certainly there have been incorrect diffs generated and caught either by CI or manual reviewers, requiring us to update the tool. We would not recommend landing diffs generated by Piranha - or other stale code removal tools - to master without any reviews right now :)
In my opinion it lies in the implementation of feature flags.
How would you introduce feature flags in a way that doesn't leave any debt behind in contrast to their approach?
Or are you saying that feature flags themselves are inheritly debt-generating (which I think they acknowledge, but are taking it on prudently & deliberately) — in which case, what would you do instead?
But seems like they accumulated a lot of flags, which stayed stale over time, and went unchecked and lack of feedback created the debt.
Also unrelated but I prefer 2 stages for retirement of flags, from production and from code. From the example in the article. Instead of using direct access to:
experiments.isTreated(RIDES_NEW_FEATURE)
I think it is better to indirectly refer this from a function like (considering we are in context of RIDES module/class):
function isNewFeatureEnabled(){ return experiments.isTreated(RIDES_NEW_FEATURE) }
So when you want to retire this flag to disable new feature but want to keep code for some time (for few versions), but don't want to bloat your binary you can simply do:
function isNewFeatureEnabled(){ return FALSE }
This is actually a feature we did identify as important to have in order to increase Piranha's effectiveness, and it is being added to our internal flag tracking, but it doesn't negate the need for the tool. An expiration date makes it easier for the tool to know when to run on a given feature (rather than using heuristics based on % rollout and days without changing the experiment), it still means that Piranha: a) reduces manual effort by auto-generating the (candidate) removal patch, and b) acts as the reminder portion for the expiration, so it's more actionable than just adding a task.
The thing to note is that, even on a steady state where flags are removed as soon as they go stale, enough new experiments are being created every day that reducing the time spent cleaning them up is valuable.
Also, you definitely don't want to block someone from fixing a crash because they have pending expired flags, so all you can really do with any expiration policy is to remind them. With Piranha, you are reminding them and reducing the friction to solve the issue. After all, the diff is right there for them to review and click 'land' on.
As for the hardcoded flag value in your example above? What does that accomplish? It looks to me like you'd only be shipping dead code, since there is no runtime way of re-enabling the `RIDES_NEW_FEATURE` behavior (which is the main difference between "rolled 100%" vs "Piranha-removed"). It also makes harder to remove the related code later, since the semantic information about it being part of the feature is lost. If it's just about having the old code available, then version control does that already, no? What am I missing?
What I was trying to say was basically, without tooling also you can manage the debt from feature flags.
Answer to the RIDES_NEW_FEATURE question is about "you definitely don't want to block someone from fixing a crash because they have pending expired flags" mainly.
When I am disabling a flag, if I set isNewFeatureEnabled to False, basically, I am removing bloat instantly. Then when I have time to review the code I can also remove the dead code. Actually this is fixing the concerns in your blog post about "accidental activation" and "bloat" without waiting developers to fix the code. Piranha can set flag to stale value, then later can send the developer task to fix the dead code.
My flow is little bit more complicated then I replied actually, I have also assets etc related to feature flags. CI pipeline also removing non-used assets for that flag when it is stale. So basically I have more like: function isNewFeatureEnabled { return isFlagEnabled(flag) && getFlagValue("RIDES_NEW_FEATURE") }
What I am curious on this topic, do you have any kind of conflict detection for your feature flags?
Can you elaborate on what you mean by conflict detection? Are you trying to understand how flags are dependent on each other?
Basically sometimes I have some conflicting flags that can introduce bugs. Especially some rarely used flag and a new feature. Basic example, 2 different flags, setting same property to different values on an UI object.
Although more testing coverage probably can help, but I am curious, if you have some automation to detect those cases.
However it works for now only with Java, Swift, or Objective-C Code.
Check it out if your projects fit those two conditions
Are Uber's Android apps written in Java? I would have thought they would be Kotlin.
I've always been a buyer of tools to make runtime stats available in the IDE
would be awesome to overlay trace / route timing into the codebase on whatever part of the call graph is on the screen