How to become 10x developer with a help of ChatGPT
… and check why 5600+ Rails engineers read also this
How to become 10x developer with a help of ChatGPT
Is being a 10x developer still a thing? Trends change, currently there’s a lot of chatter around whether LLMs will take our jobs, sorry couldn’t stand this South Park reference.
I never allow Copilot to write the full implementation for me, but its contextual suggestions are getting better and better every day. Quite tempting to use it, right? I believe that fixing things written mindlessly with put–your–favorite–llm–model–here will be a big market in the near future. It’s not much different from fixing things coming from StackOverflow, but the opponent might be harder to defeat. Time will tell.
Still, there are couple scenarios where tools backed with LLMs shine and can boost your productivity.
Writing verbose pieces of code, like tests
I prefer self–contained test cases, not dependent on shared setup and micro optimizations coming from poorly understood DRY principle. GitHub’s Copilot shines in such scenario. Let me share recent example of test which I added when implementing a value object in RailsEventStore:
specify "adapter_name" do
expect(DatabaseAdapter::PostgreSQL.new.adapter_name).to eql("postgresql")
expect(DatabaseAdapter::MySQL.new.adapter_name).to eql("mysql2")
expect(DatabaseAdapter::SQLite.new.adapter_name).to eql("sqlite")
end
First expectation was written by me, two other were suggested by Copilot, so I’ve hit tab.
Same goes for this example:
specify "MySQL doesn't support jsonb" do
expect { DatabaseAdapter::MySQL.new("jsonb") }.to raise_error(InvalidDataTypeForAdapter, "MySQL doesn't support \"jsonb\". Supported types are: binary, json.")
end
It was written entirely by a Copilot, based on other already present examples for different adapter types.
This speeds up my work, why not make a use of it?
Writing utility snippet with a library you rarely use
I recently had to find all occurrences of code like in the snippet below:
TRANSLATIONS = {
foo: t('foo'),
bar: t('bar'),
baz: t('baz'),
}
Yes, calls to I18n
library api, from inside Hash
assigned to a constant. Great optimization, but it hadn’t had a chance to work in request–response cycle where it was used. I’ve could try to find this code with carefully crafted Regex, spending half a day on it. I quickly figured out that the fastest way it would be to use excellent Parser gem for that purpose and find this code occurrences within Ruby’s Abstract Syntax Tree. I do not use this library on a daily basis, rather from time to time. Probably I’ve would written desired code within half an hour. Instead I’ve asked ChatGPT to write this snippet for me. In natural language. It took me 3 minutes to have code in a shape which solved my problem — 1/10th of a time. Can I call myself 10x developer now? See how it went:
— Write me a code snippet using Ruby Parser gem for AST parsing to get all the occurrences of hashes assigned to constants. Those hashes have method t with string argument as values.
— Sure! You can use the parser gem in Ruby to parse Ruby code and traverse the Abstract Syntax Tree (AST) to find specific patterns. In this case, you’re looking for hashes assigned to constants, where the values of the hash are calls to a method t with a string argument.
Below is a code snippet that does just that. You’ll need to install the parser gem if you haven’t already.
require 'parser/current'
code = <<-RUBY
MY_CONSTANT = { key1: t('value1'), key2: t('value2') }
ANOTHER_CONSTANT = { key: 'value' }
MY_SECOND_CONSTANT = { key3: t('value3'), key4: t('value4') }
RUBY
buffer = Parser::Source::Buffer.new('(string)')
buffer.source = code
parser = Parser::CurrentRuby.new
tree = parser.parse(buffer)
# Define visitor to handle nodes
class Visitor
def visit(node)
case node.type
when :casgn
handle_casgn(node)
end
node.children.each { |child| visit(child) if child.is_a?(AST::Node) }
end
def handle_casgn(node)
constant_name = node.children[1]
value_node = node.children[2]
if value_node.type == :hash
value_node.children.each do |pair|
key, value = pair.children
if value.type == :send && value.children[1] == :t
argument = value.children[2]
if argument.type == :str
puts "Found hash assigned to constant #{constant_name}: #{key.children[0]} => t('#{argument.children[0]}')"
end
end
end
end
end
end
# Visit the AST
Visitor.new.visit(tree)
— Print the filename and line of code additionally
require 'parser/current'
filename = 'example.rb'
code = File.read(filename)
buffer = Parser::Source::Buffer.new(filename)
buffer.source = code
parser = Parser::CurrentRuby.new
tree = parser.parse(buffer)
# Define visitor to handle nodes
class Visitor
def initialize(filename)
@filename = filename
end
def visit(node)
case node.type
when :casgn
handle_casgn(node)
end
node.children.each { |child| visit(child) if child.is_a?(AST::Node) }
end
def handle_casgn(node)
constant_name = node.children[1]
value_node = node.children[2]
if value_node.type == :hash
value_node.children.each do |pair|
key, value = pair.children
if value.type == :send && value.children[1] == :t
argument = value.children[2]
if argument.type == :str
line_number = node.location.line
line = File.readlines(@filename)[line_number - 1].strip
puts "Found in #{@filename}, line #{line_number}: #{line} - #{constant_name}: #{key.children[0]} => t('#{argument.children[0]}')"
end
end
end
end
end
end
# Visit the AST
Visitor.new(filename).visit(tree)
— Could improve it so it takes several directories as argument from command line?
require 'parser/current'
require 'find'
# Define visitor to handle nodes
class Visitor
def initialize(filename)
@filename = filename
end
def visit(node)
case node.type
when :casgn
handle_casgn(node)
end
node.children.each { |child| visit(child) if child.is_a?(AST::Node) }
end
def handle_casgn(node)
constant_name = node.children[1]
value_node = node.children[2]
if value_node.type == :hash
value_node.children.each do |pair|
key, value = pair.children
if value.type == :send && value.children[1] == :t
argument = value.children[2]
if argument.type == :str
line_number = node.location.line
line = File.readlines(@filename)[line_number - 1].strip
puts "Found in #{@filename}, line #{line_number}: #{line} - #{constant_name}: #{key.children[0]} => t('#{argument.children[0]}')"
end
end
end
end
end
end
def process_file(filename)
code = File.read(filename)
buffer = Parser::Source::Buffer.new(filename)
buffer.source = code
parser = Parser::CurrentRuby.new
tree = parser.parse(buffer)
# Visit the AST
Visitor.new(filename).visit(tree)
end
def process_directory(directory)
Find.find(directory) do |path|
if FileTest.directory?(path)
next
else
if path.end_with?('.rb')
process_file(path)
end
end
end
end
if ARGV.empty?
puts "Please provide one or more directory paths to search for Ruby files."
else
ARGV.each do |directory|
if File.directory?(directory)
process_directory(directory)
else
puts "#{directory} is not a valid directory."
end
end
end
Thanks to that I was able to rapidly locate wrong code living in app/
and lib/
directories of the application I was working on at that time.
Handling communication issues
The situation has got pretty tense and you start acting like Gordon Ramsay? There it goes:
— Write “you f*cking donkey” in a nice manner
— Certainly! A more polite way to express the sentiment could be “You’ve made a mistake.”
— How would I say that to a coworker?
— If you need to address an issue with a coworker, it’s important to be respectful and professional. You might say something like:
“Hey [Name], it looks like there was a small error here. Could we discuss how to correct it?”
This way, you bring attention to the mistake without being confrontational, allowing for a constructive conversation.
You’ve just avoided meeting with HR. Quite nice, isn’t it?
I’ve tried to look for a funny example. Obviously you can engage LLM for a more serious use like looking for arguments in support of the thesis you’re trying to advance. Or better understanding domain you’re working on if you don’t have domain expert next to you.
What’s your take on that? Hit me up on X, I’m happy to discuss other use–cases.