Chuck Conway is a software craftsman with nearly 30 years of experience. His expertise extends to Python, Generative AI, .Net, React JS, and CI/CD practices, emphasizing craftsmanship, architecture, and processes. Passionate about continuous improvement and automation, Chuck's career embodies a commitment to beautiful software and efficiency.
Chuck lives in Folsom, CA, with his wife Erin, daughter, and a cat.
Most companies follow some type of Scrum process. Typically this entails 2 or 3 week sprints. At the end of each sprint changes are demoed, retrospectives are performed and the backlog is groomed. During each sprint task completion time is captured, which allows management to project into the future when projects will reach completion.
Many of the Scrum projects I’ve been apart of emphasize “committing” to tasks or “taking ownership.” At the end of the sprint many engineers are held accountable for incomplete tasks. Sprint velocity is another idea that is hammered home. We have to keep our velocity! It’s like creating software is a race, it’s not. If engineers are held accountable by a metric, they’ll optimize for the metric, this isn’t want you want.
Scrum creates an easy to understand framework for teams to follow and it gives management the tools to predict the future. Teams that have practices waterfall find Scrum easy to grok.
Many of Scrums practices aren’t needed. For example, most issue tracking software allow managers to run reports on the frequency of ticket completion. With this information, managers are able to infer velocity, instead of baking velocity into the process and making it a big deal. Taking ownership is a farce, we do it naturally, to make it explicit is insulting. All the projects I’ve been a part of each engineer has a corner of the application that’s their space.
Other ways to improve software delivery:
If you need weekly deployments, schedule them. Deploy what’s ready.
Keep the backlog groomed; then engineers never run out of work.
In my opinion, retrospectives are the most essential non-development activity. Without it, you have no chance of becoming a better and more efficient organization.
Automate, automate, automate
Committing to a list of features is ridiculous. Rank the tasks and complete what you can. Fretting over why “task A” wasn’t complete is a waste of time. It’s clear the task was either too big, or higher priority work was taken on.
Demos are a waste of time unless the client cares and provides feedback.
Daily meetings may or may not be needed. I prefer meeting every couple of days.
At the end of the day it’s about providing value to the client in the most efficient way.
Always be measuring – don’t do it because you think it’s the right thing to do. It might not be.
Managed memory is cheap. That’s not true, allocation is cheap and garbage collection is expensive. Garbage collection stops the world while things are cleaned up and optimized. Sometimes memory cleanup can impact the performance of the application.
Performance Low Hanging Fruit
Object reuse
Object pooling. Pass in existing array rather than allocating a new one. Preallocate array length.
String concatenation.
Use StringBuilder, Preallocate length if possible.
In Code: MyParamsMethod("Hello", "World"); MyParamsMethod();
Complier: MyParamsMethod(new {"Hello", "World"}); MyParamsMethod(new [0]); // this creates a new array object in memory
Instead, do this in code: MyParamsMethod(new {"Hello", "World"}); MyParamsMethod(Array.Empty); // reuses an existing empty string array in memory. It's the same idea behind string.Empty
Suggestion: Introduce overloads with common number of arguments.
Boxing
Boxing creates a new object on the heap. Now you have two memory locations with the same value. Changing either value does not impact the other value.
Suggestion: Introduce generic overloads
Closures
The complier converts closures to classes. Captured values are passed in as constructor parameters. This class is then allocated to the heap.
Suggestion: Avoid critical paths. Pass state as argument to lambda. Investigate local functions. Since local functions lifetime is known, it can be allocated on the stack where allocation and cleanup are cheap.
LINQ
Lambda expressions are treated the same as Closures. They are allocated as a class to the heap. Because much of LINQ is based on static methods, additional allocations of Iterators and IEnumerable happen.
Suggestion: Avoid critical paths. Use good old foreach and if statements
Iterators
iterators are rewritten as a state machine, which means more allocations to the heap.
Suggestion: Return a collection. Be aware of the cost.
async/await
Async/await also, generate a state machine. Task and Task also trigger more allocations which then can’t be reused.