3 weeks of agentic coding

use gh and tell me how many PRs I have authored the last 3 weeks. from these PRs how many are merged? what was the total of added and removed lines?

Your PR Statistics (Last 3 Weeks)
 - Total PRs authored: 31
 - Merged PRs: 27 (87% merge rate)
 - Total lines added: 4,095
 - Total lines removed: 2,758
 - Total lines changed: 6,853
You've been quite productive! Most of your PRs from the last 3 weeks are merged, with a solid mix of refactoring and feature work.

Up until now the main way of using LLMs was through chat. Every time I wanted to do a deep dive on a technical issue I was starting a conversation through which I was getting a better understanding both on the code and the way it should be used.

I had played with the agent tab of copilot in android studio but never given it too much time. That was the reason I believed that it is not that good!

So, I decided to give it a real shot and use it foul time, on production code, in a project that is important. The goal was to write as little code as possible.

The workflow I ended up having

  1. Understand the task at hand and create a mental model of the solution.
  2. Figure out the steps I need to make in order to implement the solution. If the steps are too many I break them into groups.
  3. Start writing these [group of] steps in a prompt where I ask the agent to provide me a plan with the intended changes.
  4. Review the plan, ask the agent to make adjustments (repeat this step as many times as needed).
  5. Ask the agent to save the plan in a markdown file.
  6. Ask the agent to execute the plan.
  7. Review the changes.
  8. If something trivial needs to be changed I do it myself, if the change cascades through many files I tell the agent to do it.
  9. In the second case I also request an update to the plan.
  10. Final review, commit and push.

The prompts must not be too detailed but also not too general. For example:

Take a look at <file #1> and <file #2> and give me a plan with all needed changes in order to:
1. Start <component #1> as disabled
2. Enable it every time the user selects an address (<component #2>) or
3. Enable it every time the user is typing a new zipcode (<component #3>)

Mistakes

It goes without saying that to end up in the above workflow I did many mistakes. Here are the big ones.

Provide the outcome

At first my prompts were a simple description of the outcome I wanted. I thought it will figure things out, make the necessary connections and write exactly what we need. Nope. The agent knows what you allow it to and when it can’t find something it simply creates random solutions.

Straight to the execution

My interaction with the agent was starting by asking it to do something. No plan at all. In simple cases this might be fine but when having a change that touches many components, a simple adjustment, after the agent’s work, might end up in updating a lot of code or in more adjustments.

Getting greedy, asking too much

After making some progress and saw how effective I was I got greedy. I started asking too much from the start and ended up with massive PRs that included changes often unrelated to each other.

Tips

Always have a plan first

  • For me having a plan gives me ease. I am more certain that things will be done as intended because they will be done they way I want to!
  • Through the process of making the plan there will be times that you will understand better the code at hand and figure out missing cases.
  • Especially for repetitive tasks the plan speeds things tremendously:
    I had to migrate a few screens from one pattern to another. I did the first migration using the agent (through a plan etc) and when finished I asked it to change the plan in such a way that will accept “parameters”. After that I just fed the updated plan with the next screen to the agent.
  • It is a memory that can be fed in any agent, in a clean context window, at any time.

Use the agent to figure things out

Some times in order to build the mental model for the solution you need to understand the code better. Use the agent to do that. See how it articulates things and then ask it to save its findings in a file. That file can be part of the plan:

see how component A works by reading file <name>

Always review the code

Perhaps the most important tip of all. Don’t add code to the project that you don’t know what it does. Always review what the agent did. Make sure that it follows the project’s conventions and standards. The fact that it was written by an agent does not mean that it is not your code. You are responsible for it. It is your solution, you just used a different medium to implement it.

Explore more, it is fast now

The benefit of having a tool that implements your thoughts way faster than you is that you can explore multiple solutions! Use git to make different branches/checkpoints and try every approach you thought of.

Keep things small

You can use an agent to implement an entire task but if you break it and do groups of changes then your reviews will be easier and quicker which means that your understanding of the changes will be better.

Bonus

I keep a repo with the Gilded Rose kata. Every now and then I create a new branch and practice on the kata.

This time the practice required to use only an agent. You can see the branch here and the prompts I used here (i asked the agent to save them to a file).

I guess i’m an engineering manager now

It took me two and half years to come to terms with my new role. So, why now? I guess its a process like everything else. You start something new and feel completely out of your league. You educate yourself and try to apply your learnings. You make mistakes and feel awful. You try something else, make progress and feel better. A roller coaster of feelings! But you keep on pushing and some day you wake up knowing exactly what your job is.

The individual contributor to engineering manager transition

I don’t know about other industries but in software development the transition from an IC to EM is a bit hard. You have to leave a role where everything can be solved with code and where you can showcase your progress and your skills and move to a role that you might not write code for weeks!

So it took me all that time to accept the transition and most importantly to embrace the fact that it is OK to do or not to do a few things.

It’s OK to…

It’s OK not knowing everything and not having all the answers. My job is not to be a dictionary. My job is to be there, help in the research and assist on picking the more suitable solution.

It’s OK not solving everything myself. My job is not being a 10x developer. My job is building a 10x team. So when a problem occurs, I have to define it properly, set a few guidelines and let someone else do the solving.

It’s OK not being the first figuring out that there is a problem. Being a leader does not mean that I have to monitor everything and prevent problems before they occur. My job is to listen when someone brings an issue, assess the situation and prioritise any work that needs to be done while having the best interest of both the team and the codebase.

It’s OK not writing code that much. My job is not measured by the features I implement any more. My job is measured by the features my team is implementing and the level of quality the project has. I might not write code but I do write everything else. Goals, guidelines and documents that will help the team align and work towards the same end.

It’s OK not to be the first to speak. I’m not here to be heard. My job is to foster an environment where everyone has a say.

It’s OK not be right, it’s OK to ask for help. Being in a leading position does not mean you have to do everything perfect. My job is to build a healthy team that its members support each other. What a better way to do that by setting the example that we can all be wrong from time to time and that we will need someone to guide us.

Changing my status

I truly believe that a title in LinkedIn, or anywhere else, does not mean a thing on its own. Having said that I will be updating my title in LinkedIn because (a) I finally feel more comfortable with my role and (b) it is a psychological hack to help me invest even more in my new craft.

PS: I will still answer software engineer when someone asks me what I do for a living 😛

Wax on, wax off, paint the fence, paint the house

Kata is a Japanese word meaning “form”. It refers to a detailed choreographed pattern of martial arts movements. It can also be reviewed within groups and in unison when training. It is practiced in Japanese martial arts as a way to memorize and perfect the movements being executed.
Wikipedia

I don’t have a relationship with martial arts so I’ve learned the meaning of kata through code katas. A series of exercises that, through repetition, help in learning and understanding a pattern, an approach or even a technology/tool.

New benefits each time

In my mind a code kata is a list of specific steps that you apply one after the other in order to implement something. You do it enough times and the steps become muscle memory.

The red-green-refactor cycle, when trying to develop something test first, is one example. Write the test, watch it fail, write the code to make it pass, watch it pass, refactor the code to make it good! My favorite kata for practicing TDD is the Bank.

Another list of steps is when you want to refactor a piece of code. First, you write tests to ensure that your changes will not break the code’s behaviour. Then you start making changes by extracting code to new methods or classes. Each change is followed by an execution of the test suite to make sure that everything still works. Repeat until you are done. My favorite kata for practicing these steps is the Gilded Rose1.

a quick note here: both links above, apart from the exercise, contain a great video that showcases the kata

Practicing a code kata results in building your knowledge bit by bit. You know that each try helps in building that muscle memory and you keep on do it. The first time you simply apply the steps and enjoy the result but from that point on, each new practice helps you to understand why each step is needed. You start feeling more comfortable and begin to experiment by tweaking the steps a bit. Until one day you just know how and when to use the steps.

Be patient Daniel-san

On the other hand Mr. Miyaki’s wax on – wax off approach gives you no immediate knowledge or satisfaction. You simply repeat a small and tedious exercise feeling that there is no meaning in all that. What you don’t realize is that each repetition adds a small brick in building that memory. Until one day you wake up and your reaction is perfect and without you even think about it!

Why am I telling you all that? Because this week I realized that some times we need to be Mr. Miyaki. We need to push ourselves and our colleagues to do, and repeat on doing, the small and tedious change. The change that does not offer an immediate great value but its repetition helps in creating a muscle memory that contributes, at all times, on keeping the project clean and the team focused on the same goal.

“Act as if the API was providing it”

An example of such change is the request I did in a PR where the UI code looked like this (jetpack compose):

if (items.size > 2) {
  Button(...)
}

Here 2 is a hard coded magic number, given by the business and known only by the mobile client. The request was to move this “threshold” to the model that gets created from the API’s response deserialization. Then change both our domain and presentation objects and act as if the API was providing the value all along.

The repetition of this particular wax on/off has three advantages:

  1. It keeps the project’s architecture clean by moving all hard coded values to the outer layer.
  2. It cultivates the thinking that the mobile client must always be dynamic and have everything provided by the API.
  3. If the business decides to play with this threshold, the change in the mobile client will be quick and trivial

“Lets keep using this boolean property”

Another example comes from a discussion I had with a colleague of mine regarding a presentation object that looked like this:

class Foo(
  val title: String,
  val showTitle: Boolean
)

The question was if there is a need to keep the boolean property. Is there a value on having presentation objects that detailed? My answer was yes, there is a value and the repetition of such implementations has its own advantages:

  • It maintains the project’s convention which dictates that a view must be as dummy as possible. The view will not have to use the title to figure out if it has to hide it or not. It just consumes the boolean property.
  • It cultivates a certain approach, when designing such presentation objects, that makes the entire presentation layer more powerful since it handles many aspects of the UI.
  • Having such a layer allows testing to indirectly assert the UI behaviour.
  • That on its own is a good motivation for the devs to write more tests and grow their skills and thinking even more.

Be both Mr. Miyagi and Daniel

Be like Miyagi and push for the repetition. Be like Daniel and keep on doing the exercise even if you think there is no point at it. There will be a time that the change will come naturally and the entire team will have the same mindset. This will keep the project at a great level.

The team will kick a$#:

  1. if you want to practice gilded rose in kotlin I have a repo to help you started ↩︎

50 posts in 5 years

WordPress informed me this morning that my subscription will be auto-renewed in a couple of weeks. That got me thinking. Am I spending money for no reason? Its been a while since the last time I wrote a post and even more since I was writing regularly.

The benefits

Has this blog helped me somehow? For that I am sure, it has!

Better communicator

Back in 2019, when I started writing, I already had a ten years experience on programming.
I knew how to do things but I was having trouble on passing that knowledge to my co-workers.
Why? I was never providing the entire context. My mind was skipping bits just because it was taking them for granted. I know them so my listeners most probably know them too!

It was blogging, and the fact that I did not have someone in front of me to ask a question or to be confused, that forced me into always building a proper context. Into taking the time to organize my thoughts, trying to break them to small pieces, putting them in the proper order and to give all necessary examples.

A skill that started here but soon enough moved into emails, messaging and finally talking live (I’m pretty sure I get less confused looks :P).

Deeper knowledge

Writing to a public blog makes you feel exposed. So I started to second guess myself and think that a mistake in my examples will bring thousands of years of shame upon my family!

So I started researching. I read articles, books and anything that is related to what I was about to write! I dove into GitHub and any public code of the framework/language I was using.

At the end, I was spending many hours for a ten lines blog post, but I knew a lot more about the post’s subject!

Personal branding

I started writing because I wanted to express a few of my believes and approaches on programming. Watching how others were doing it, I mimicked them and after each post I was also making a tweet and a LinkedIn post.

That got me new connections, a couple of cool discussions and a few job offers.

In other words, I now have a small place in the internet that anyone can visit and see a few things about me and my work.

Small dopamine doses

I won’t lie. I like it when a post of mine gets featured, or gets a thank you comment. I like it when I see in my stats a team’s tool as a referrer. There is a team out there that uses one of my posts as learning material!

Just start writing

So, am I spending money? Nah. I might write less often but I like having this place. At the end of the day it helps me grow and that is enough.

Should you do it?

Don’t worry if someone else has written about the topic you want. Don’t worry if you don’t have a specific style at first. Don’t worry if no one will read it. Just write about something you want. Something you learned, something you discovered, something that puzzles you. Just start writing!

Relax, take a step back and start from the business logic

Another year of advent of code and this time I decided to participate. Not only that but I thought it would be a great way to teach myself Ruby.
This means that my train of thought gets interrupted quite often since for every little thing I come up with I have to stop and search how Ruby does it.

Day 3 – Gear Ratios

In today’s challenge the target is to calculate the sum of all numbers that are adjacent, even diagonally, to a symbol. The input looks like this:

467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

and a symbol is everything that is not a number or a dot (.).

Head first

Seeing this input combined with the fact that I don’t know Ruby throw me in a crazy rabbit hole where I was searching for two-dimensional arrays at one point and parsing strings at another.

Then I remembered that the adjacent part includes diagonals too so I dropped everything and start thinking of how I will combine numbers from one line with symbols from another.

This is getting big. Should I start smaller? Should I try to approach this in a 2×2 array? Should I do this or that? Chaos!

Start from the business logic

Thankfully after taking a break and drinking some water I realized that my need to answer all my unknowns had taken the best of me and I was viewing things wrong.

It does not matter how the input looks. It is just that, an input. I shouldn’t start from there.
It does not matter what/how Ruby does things. It is just a tool.

What matters is the business logic and in this case its quite simple:

  • Our business entities are Numbers and Symbols.
  • Our business logic dictates that a Number is next to a Symbol if it lies to the area that surrounds it.

Translating this to code:

class Symbol
attr_reader :value, :row, :column
def initialize(value, row, column)
@value = value
@row = row
@column = column
end
end
class Number
attr_reader :value, :row, :starting_column, :ending_column
def initialize(value, row, starting_column, ending_column)
@value = value
@row = row
@starting_column = starting_column
@ending_column = ending_column
end
def is_next_to?(symbol)
return false if (@row <= symbol.row – 2) || (@row >= symbol.row + 2)
return false if (@ending_column <= symbol.column – 2) || (@starting_column >= symbol.column + 2)
true
end
end
view raw gear_ratios.rb hosted with ❤ by GitHub

made things so much simpler:

class GearRatios
def initialize(numbers, symbols)
@numbers = numbers
@symbols = symbols
end
def sum
@numbers.filter { |number| @symbols.any? { |symbol| number.is_next_to? symbol } }
.sum { |number| number.value }
end
end

After writing and testing the business logic all I had left to do was to write the code that will produce our lists for numbers and symbols. In this case it just happens to be a two-dimensional array with string values.

Start from what matters

Being overwhelmed or not, relax, take a step back and start from what matters.

Presentation is important but it shouldn’t drive an approach since it might change often. Input is also important but it shouldn’t matter if we are dealing with a database, a web service or the file system.

Start from the business, make it work and then try to figure out how everything else can be plugged in.

Bring me both your problems and your suggestions

It’s been a little over a year that I have the role of an engineering manager for the first time in my career. Needless to say that I’m still learning. I read the dos and don’ts and try to incorporate them in my everyday work life. Sometimes everything works fine, some others everything goes wrong!

The don’t I keep doing

The one thing that I often find myself doing is providing, immediately, a solution to a problem that was brought to my attention. No “tell me what you think we should do”, no “what approaches have you tried?”, nothing that will spark a dialogue between me and my report. A dialogue that will helps us to figure things out and grow as engineers.

Figuring something on your own, instead of being told about it, has proven to be vital in understanding it better. So, if I want my reports to grow as engineers, I must lead them to a solution rather than give them one. The aforementioned questions must be my first reaction.

The do we can start doing

this part is a message to all my current and future reports

My hope is that writing about this don’t will make me more conscious about this behavior and avoid doing it. But, we are all humans and it will be better to have a safeguard.

So, what I propose is for you to come to me both with a problem and a suggestion as to how we can solve it. This will keep me from offering an answer right away and, most importantly, it will help you grow as an engineer. Forcing yourself to think about a suggestion or to reflect upon the approaches you’ve taken will make you understand the problem and its domain better. That alone will benefit our discussion or, even better, might lead you in a solution without any help!

Let me be a feedback loop for you

(this post was written with the intent to give it to my current, and future, direct reports in an attempt to establish part of our relationship)

Every time we need to solve a problem (or implement a feature) the process is the same

  1. First we think the approach we are going to follow. This is an abstract flow that we believe will solve the problem.
  2. Based on that we divide our approach to one or more components (modules / classes / functions, whatever suits the size of the problem) and assign a certain behavior to each one of them.
  3. Finally, we start implementing each component.

Feedback loop

How do we know if something we implemented is correct?

By getting feedback. Based on it we make improvements and try again. This loop keeps us on track and allows us to deliver something valuable.

This is why we try to have frequent and short feedback loops. This way any corrections occur sooner than later and we don’t spend time and effort into something that might, potentially, thrown away.

Tech lead as a feedback loop

Looking back to the process of solving a problem we could say that two out of the three steps do have a feedback loop to helps us.

In the implementation step we can argue that the compiler / linter / etc gives us immediate feedback on what we wrote. We fix it and move forward.

For the behavior step we get the feedback through tests. Is the component’s behavior the expected one? How about the components API? Can we use it easily in our tests? Answering these question helps in having solid components.

But what about the approach step? This is where the tech lead comes in.

Right before writing any code, thus putting effort into something, prepare a simple list with the steps you are about to take and discuss them with your tech lead. If there is any feedback use it to improve your approach and have another meeting.

Is it time consuming?
No. In case the approach needs improvement after the implementation the time to fix it will be longer.

Do you feel bad on spending your TL’s time?
Don’t. It is, literally, their job to help you.

I think that data classes help in violating the YAGNI principle

Let me ask you something. You are the reviewer in a PR that creates a simple calculator which needs to know how to add numbers. Just that. There are no reasons to make us think that the calculator will need more functionality.

Despite that the PR includes a calculator that can add, subtract and multiply. What do you do as a reviewer?

I want to believe that you will, respectfully, discuss the removal of the extra functionality otherwise the calculator will violate the YAGNI principle and add (a) more code for the developers to maintain and (b) more ways to couple the project with the calculator. And all that with no immediate benefit.

Do we need all that functionality?

The same goes with data classes. There is no reason to have a class that can be uniquely identified by all of its properties if we don’t use its instances this way. There is no reason to have an extra getter for every property if we don’t use, extensively, the destructuring declaration. There is no reason to have a copy mechanism if we never copy instances!

If it is there it is going to be used

I recently removed the data keyword from one of our oldest classes and I noticed that many of our newest tests started to fail in compilation. The compiler could not find the copy method which was used to create dummy values from other dummy values by changing one property per test.

When to create a data class?

Here is my thought process when trying to decide the type of class I’ll use:

Q: Is this class anything but a domain entity or value object?

A: Then a simple class is just fine.

Q: Is this class a domain entity? Meaning that it can be uniquely identified by a subset of its properties (ex: an id)?

A: Then a simple class with an implementation of equals/hashCode will be enough.

Q: Is this a value object? Meaning that it can be uniquely identified by all of its properties?

A: Yes.

Q: How many properties?

A: One. Then a value class is a must.

Q: Are you sure its just one?

A: Turns out its more! We’ll use a data class.

Don’t do it for the test

Our test code is the first consumer of our production code. Changing the production code, in this case change/create a class as data, to write more quickly a couple of tests will result in having tests that can easily break every time the production code changes since the tests know too much about the code’s internals and not its behavior.

Don’t be afraid to throw your work away

There are times, especially in large code bases, that you might be working towards a solution and get stuck because of something that you did not foresee.
That was my case this past week. In order to unblock the development of a new feature I decided to change the API of some classes. The changes would make the integration with the feature much easier and intuitive.

I moved some code, deleted some other, made a few additions and after 3 days I had the API I aimed for. Unfortunately this new API, even though it was great for the new feature, it did not play well with a certain flow. A flow that was not affected by the old API.

In other words by fixing one thing I broke another. So I did the only logical think to do.. I deleted the branch I working on!

Always weigh things

I have to admit that deleting a piece of code that you have worked for hours is not an easy decision. Especially when it looks and behaves as you have designed it. The urge to keep changing things in order to make all flows work is quite strong.

This is where you have to weigh things. Is it worth the effort? Do we have the time to invest? Will the final code be clean, scalable, readable?

In my case the decision to move forward and try to include the broken flow would mean tieing things together (bad code) and also adding a couple more days of work (more time). It wasn’t worth it.

Clean mind

A benefit of throwing a solution is that you can now see the other routes that where there from the start but you were too focused to notice them. Be it that you are no longer occupying your mind with the previous solution’s graph, be it that you have to figure something out, almost always you’ll find another way to tackle things.

In my case the new approach was way simpler and easier. The old API was left untouched and the entire integration was achieved from a different point that up until the deletion I hadn’t given it much attention.

Micro throwing

Throwing implementations is great for small things too like functions or new classes.
Every time I develop one of them, if I start to feel that things are slowing down I don’t think of it much, I just reset --hard and start over (it always helps if you already have a couple of tests to back you up).
Having one route crossed out and knowing, at least some part of the solution, I find the second, third etc implementation to be much faster.

Code review: don’t just request a change

When reviewing a PR I try to elaborate on my proposals for two reasons:

1. Out of respect to my colleague

Every piece of code is part of an effort that took time and thought. Requesting a code change by simply asking it (i don't like it, change it) or by dictating it (do it this way) demotes all the work that has been done.

I figured that, since my colleague spent a few hours to come up with a solution I owe her/him more than a few seconds!

2. I solidify my knowledge or even better, learn something new

By trying to justify the reason behind a request I end up understanding better why I prefer a solution or a format over another.

When I provide an example or when I do a mini investigation to help me clarify why something must change I often find that (a) many things that I thought I knew were not as I had them in mind and (b) I now understand a concept much better because I had to write about it. You don’t master something if you can’t explain it!

PS: never forget that it might say “request a change” but in reality you start a conversation between two professionals that will eventually benefit of the project