Issue 1, automate your developer workflows
This newsletter is really directed at anyone in the age of AI assistant. My hope is that you by reading this newsletter, you get ideas on how to use AI assistants to automate your work and make your life easier.
What a developer does
Being a developer means you do a lot more things than just writing code. Things you do are usually something like so:
In a larger company
In a larger company you might have the luxury of doing a smaller subset of the above like:
as the other tasks are handled by other teams.
Ok, so let's say you're only working on these four tasks above, how can an AI tool help me? Well, there are usually two major types of projects you're working on:
Depending on the type, you would use an AI tool to solve different tasks.
Greenfield projects
In a greenfield project, you start with a blank sheet, so how can I get help? If you're a web developer, many times the job is about building a Web API. Of course, saying that, there's a lot of assumed context what that means, there are concepts like REST, routes, controllers, models, query parameters, headers and so on.
Example - building a Web API using AI
You've done this task many many times before manually, so let's do it again, but this time using an AI tool.
First, let's create a prompt for the AI tool to use (use Chat GPT or GitHub Copilot Chat for this one):
Create a Web API that has routes customers, products and orders, should support HTTP verbs GET, POST, PUT, DELETE and use Python and Flask.
The above prompt does two things:
Here's the outcome of the prompt:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
@app.route('/customers', methods=['GET', 'POST'])
def handle_customers():
if request.method == 'POST':
if not request.is_json:
return jsonify({"msg": "Missing JSON in request"}), 400
name = request.json.get('name', None)
if not name:
return jsonify({"msg": "Missing name parameter"}), 400
customer = Customer(name=name)
db.session.add(customer)
db.session.commit()
return jsonify({"msg": "Customer created", "customer": name}), 201
customers = Customer.query.all()
return jsonify([customer.name for customer in customers])
@app.route('/customers/<int:id>', methods=['GET', 'PUT', 'DELETE'])
def handle_customer(id):
customer = Customer.query.get(id)
if not customer:
return jsonify({"msg": "Customer not found"}), 404
if request.method == 'PUT':
if not request.is_json:
return jsonify({"msg": "Missing JSON in request"}), 400
name = request.json.get('name', None)
if not name:
return jsonify({"msg": "Missing name parameter"}), 400
customer.name = name
db.session.commit()
return jsonify({"msg": "Customer updated", "customer": name}), 200
if request.method == 'DELETE':
db.session.delete(customer)
db.session.commit()
return jsonify({"msg": "Customer deleted"}), 200
return jsonify({"customer": customer.name})
That is quite an interesting response, we actually got more than we asked for as it gives us the following:
Improving the code
Of course, at this point, the code is not perfect, but it's a good starting point. You can keep working on this code and improve it either manually or using an AI tool, let's do the latter by crafting a new prompt (here's where your knowledge as a dev comes in, you're the pilot, it's the copilot, you're in control).
Type this prompt to improve the code (yes, your AI assistant usually doesn't need more than that for context):
improve the above code
That's not a lot to go on, but let's see what we get, improve "how" you might ask yourself:
First you get a text response that explains what the AI assistant thinks you want to improve:
The provided code is a good starting point for a Flask API. However, there are several improvements that can be made to make the code more robust and maintainable:
1. **Separate the models into a different file**: This will make the code more organized and easier to maintain.
2. **Use Marshmallow for Serialization**: Marshmallow is a popular library for object serialization/deserialization. It can be used to validate input data and serialize output data.
3. **Use Flask-Restful**: Flask-Restful is an extension for Flask that adds support for quickly building REST APIs. It encourages best practices with minimal setup.
4. **Error Handling**: Add error handling to return meaningful error messages to the client.
Here's how the improved code might look:
and then comes the code:
# models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
# app.py
from flask import Flask
from flask_restful import Api, Resource
from models import db, Customer, Product, Order
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
class CustomerResource(Resource):
def get(self, id=None):
if id:
customer = Customer.query.get(id)
if not customer:
return {"error": "Customer not found"}, 404
return customer.json()
customers = Customer.query.all()
return [customer.json() for customer in customers], 200
# Implement POST, PUT, DELETE similarly
api.add_resource(CustomerResource, '/customers', '/customers/<int:id>')
# Similar resources would be created for 'products' and 'orders'
This looks like a lot less code, but it's
At this point, you could really go in any direction, like for example:
and above is really the beauty of it, it's like driving a car, you're in control, you can keep going in the same direction, or take a turn, or stop, or go back, it's up to you.
Fixing the output - give me the missing code
But let's try to actually see if it's able to complete the code for us, by trying this prompt:
you're missing code, please add missing implementations to include all resources and all HTTP verbs
As a developer, I can tell what's missing, let's see if the AI assistant can figure out what I need it to do:
Here's the outcome:
Sure, I'll add the missing implementations for the Product and Order resources. Here's how you could implement these resources using Flask-Restful:
Ok, sounds promising, let's see the code:
# models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
# app.py
from flask import Flask
from flask_restful import Api, Resource
from models import db, Customer, Product, Order
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
class CustomerResource(Resource):
def get(self, id=None):
if id:
customer = Customer.query.get(id)
if not customer:
return {"error": "Customer not found"}, 404
return customer.json()
customers = Customer.query.all()
return [customer.json() for customer in customers], 200
# Implement POST, PUT, DELETE similarly
api.add_resource(CustomerResource, '/customers', '/customers/<int:id>')
class ProductResource(Resource):
def get(self, id=None):
if id:
product = Product.query.get(id)
if not product:
return {"error": "Product not found"}, 404
return product.json()
products = Product.query.all()
return [product.json() for product in products], 200
# Implement POST, PUT, DELETE similarly
api.add_resource(ProductResource, '/products', '/products/<int:id>')
class OrderResource(Resource):
def get(self, id=None):
if id:
order = Order.query.get(id)
if not order:
return {"error": "Order not found"}, 404
return order.json()
orders = Order.query.all()
return [order.json() for order in orders], 200
# Implement POST, PUT, DELETE similarly
api.add_resource(OrderResource, '/orders', '/orders/<int:id>')
Ok, so I got the missing resource implementation, but it's still missing the HTTP verbs, let's see if it can figure that out, with another prompt:
thanks, better, but you are still missing HTTP verb implementations, please add that
Ok, let's see what we get:
# app.py
from flask import Flask, request
from flask_restful import Api, Resource
from models import db, Customer, Product, Order
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
class CustomerResource(Resource):
def get(self, id=None):
if id:
customer = Customer.query.get(id)
if not customer:
return {"error": "Customer not found"}, 404
return customer.json()
customers = Customer.query.all()
return [customer.json() for customer in customers], 200
def post(self):
name = request.json.get('name')
new_customer = Customer(name=name)
db.session.add(new_customer)
db.session.commit()
return new_customer.json(), 201
def put(self, id):
customer = Customer.query.get(id)
if not customer:
return {"error": "Customer not found"}, 404
customer.name = request.json.get('name')
db.session.commit()
return customer.json(), 200
def delete(self, id):
customer = Customer.query.get(id)
if not customer:
return {"error": "Customer not found"}, 404
db.session.delete(customer)
db.session.commit()
return {"message": "Customer deleted"}, 200
api.add_resource(CustomerResource, '/customers', '/customers/<int:id>')
# Similar resources would be created for 'products' and 'orders'
So, this wasn't quite there, but it's getting closer, it's missing the OrderResource and ProductResource implementations, but it's getting there.
One last prompt, hopefully?
Code is still missing, please add implementations for all resources and all the HTTP verbs.
and the answer is:
I apologize for the confusion earlier. Here's the complete implementation of the HTTP verbs for each resource:
# app.py
from flask import Flask, request
from flask_restful import Api, Resource
from models import db, Customer, Product, Order
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
class CustomerResource(Resource):
# ... existing code ...
api.add_resource(CustomerResource, '/customers', '/customers/<int:id>')
class ProductResource(Resource):
def get(self, id=None):
if id:
product = Product.query.get(id)
if not product:
return {"error": "Product not found"}, 404
return product.json()
products = Product.query.all()
return [product.json() for product in products], 200
def post(self):
name = request.json.get('name')
new_product = Product(name=name)
db.session.add(new_product)
db.session.commit()
return new_product.json(), 201
def put(self, id):
product = Product.query.get(id)
if not product:
return {"error": "Product not found"}, 404
product.name = request.json.get('name')
db.session.commit()
return product.json(), 200
def delete(self, id):
product = Product.query.get(id)
if not product:
return {"error": "Product not found"}, 404
db.session.delete(product)
db.session.commit()
return {"message": "Product deleted"}, 200
api.add_resource(ProductResource, '/products', '/products/<int:id>')
class OrderResource(Resource):
def get(self, id=None):
if id:
order = Order.query.get(id)
if not order:
return {"error": "Order not found"}, 404
return order.json()
orders = Order.query.all()
return [order.json() for order in orders], 200
def post(self):
customer_id = request.json.get('customer_id')
product_id = request.json.get('product_id')
new_order = Order(customer_id=customer_id, product_id=product_id)
db.session.add(new_order)
db.session.commit()
return new_order.json(), 201
def put(self, id):
order = Order.query.get(id)
if not order:
return {"error": "Order not found"}, 404
order.customer_id = request.json.get('customer_id')
order.product_id = request.json.get('product_id')
db.session.commit()
return order.json(), 200
def delete(self, id):
order = Order.query.get(id)
if not order:
return {"error": "Order not found"}, 404
db.session.delete(order)
db.session.commit()
return {"message": "Order deleted"}, 200
api.add_resource(OrderResource, '/orders', '/orders/<int:id>')
Ok, let's analyze what we got:
and the conclusion is that we got a complete Web API implementation, but we're probably wondering if a better crafted prompt would have reduced the back and forth.
And now, let's pivot
Let's do a final thing, namely let's simplify the code to just use static data, it might be too early to use a database if we for example don't know if the API is going to be used or not, so let's do that:
please remove SQLAlchemy and only use static data in the API, thanks
and the resulting code:
# app.py
from flask import Flask, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
# Static data
customers = [
{"id": 1, "name": "John Doe"},
{"id": 2, "name": "Jane Doe"}
]
products = [
{"id": 1, "name": "Product 1"},
{"id": 2, "name": "Product 2"}
]
orders = [
{"id": 1, "customer_id": 1, "product_id": 2},
{"id": 2, "customer_id": 2, "product_id": 1}
]
class CustomerResource(Resource):
def get(self, id=None):
if id:
customer = next((c for c in customers if c["id"] == id), None)
if not customer:
return {"error": "Customer not found"}, 404
return customer, 200
return customers, 200
# Implement POST, PUT, DELETE similarly
api.add_resource(CustomerResource, '/customers', '/customers/<int:id>')
class ProductResource(Resource):
def get(self, id=None):
if id:
product = next((p for p in products if p["id"] == id), None)
if not product:
return {"error": "Product not found"}, 404
return product, 200
return products, 200
# Implement POST, PUT, DELETE similarly
api.add_resource(ProductResource, '/products', '/products/<int:id>')
class OrderResource(Resource):
def get(self, id=None):
if id:
order = next((o for o in orders if o["id"] == id), None)
if not order:
return {"error": "Order not found"}, 404
return order, 200
return orders, 200
# Implement POST, PUT, DELETE similarly
api.add_resource(OrderResource, '/orders', '/orders/<int:id>')
Ok, so now we lost the database, but we got static data, but it's still using the flask_restful library. I think you've seen enough on how to work with an AI assistant to build a Web API :)
A second example- brownfield, let's have it do boring stuff for us
In our first example, we used an AI assistant to build a Web API, but that might be too much, maybe your day-to-day is more about creating classes, adding methods, removing or adding fields, and so on, on existing code? The AI assistant can help even with those scenarios, let's see how.
Recommended by LinkedIn
Create a class
Creating a class, we've done that hundreds of times, we probably are using a snippet for it already. But let's have our AI assistant do it for us with the following prompt:
create a Product class with fields name, id, description, updated, created
From what you can see above, we just give it the name of things, not what type they should be, let's hope for the best crossing fingers :)
Here's the response:
class Product:
def __init__(self, name, id, description, updated, created):
self.name = name
self.id = id
self.description = description
self.updated = updated
self.created = created
Oh, I wasn't specific, I meant I wanted it in TypeScript, let's try again (yes it can produce code in many languages):
create a Product class with fields name, id, description, updated, created in Typescript
with result:
class Product {
id: number;
name: string;
description: string;
updated: Date;
created: Date;
constructor(id: number, name: string, description: string, updated: Date, created: Date) {
this.id = id;
this.name = name;
this.description = description;
this.updated = updated;
this.created = created;
}
}
Nice! Now you saw that the AI assistant can do many programming languages as well. What about adding a method to the class, let's try that:
add a method fullDescription that interpolates name, description
with result:
class Product {
id: number;
name: string;
description: string;
updated: Date;
created: Date;
constructor(id: number, name: string, description: string, updated: Date, created: Date) {
this.id = id;
this.name = name;
this.description = description;
this.updated = updated;
this.created = created;
}
fullDescription(): string {
return `${this.name}: ${this. Description}`;
}
}
Let's try improving the code, let's see what we get:
improve the above code
we get this response:
class Product {
constructor(
public id: number,
public name: string,
public description: string,
public updated: Date,
public created: Date
) {}
fullDescription(): string {
return `${this.name}: ${this. Description}`;
}
}
Nice, now we're talking.
Final experiment - RegEx
Regular expressions, RegEx, are a powerful tool, but they are also hard to get right, and let's be honest, we don't use them that often, so we forget how to use them. Can an AI assistant help us with that? Let's try it out.
Here's the RegEx we want to understand:
let emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
The name gives it away, it's about email, but can we get more information about it? Let's try:
explain emailRegex
Ok, let's see what we get:
The emailRegex is a regular expression used to validate email addresses. Here's a breakdown of how it works:
- ^: This asserts the start of a line.
[a-zA-Z0-9._%+-]+: This matches one or more (+) of the enclosed characters. It includes lowercase letters (a-z), uppercase letters (A-Z), numbers (0-9), and the special characters . _ % + -.
- @: This matches the @ symbol.
[a-zA-Z0-9.-]+: This matches one or more (+) of the enclosed characters after the @ symbol. It includes lowercase letters (a-z), uppercase letters (A-Z), numbers (0-9), and the special characters . and -.
- \.: This matches the . symbol.
- [a-zA-Z]{2,}: This matches at least two ({2,}) of the enclosed characters. It includes lowercase letters (a-z) and uppercase letters (A-Z). This is typically used to match the domain part of the email (like com, org, net, etc.).
- $: This asserts the end of a line.
Ok, great, we understand it a bit more, but let's do some scary stuff, namely update the RegEx to match a new condition, namely that the email address should only be allowed for the following domains: gmail, yahoo, hotmail, outlook, nothing else. Let's try it:
Update emailRegex to only allow gmail, yahoo, hotmail, outlook
Here we go :)
let emailRegex = /^[a-zA-Z0-9._%+-]+@(gmail\.com|yahoo\.com|hotmail\.com|outlook\.com)$/;
It looks like it could work, but as well know with code, we don't trust it unless we got tests that proves it, so let's create tests next:
generate tests for all possible permutations of the emailRegex
and the response (in Jest code):
describe('emailRegex', () => {
test('valid emails', () => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@(gmail\.com|yahoo\.com|hotmail\.com|outlook\.com)$/;
expect('user@gmail.com'.match(emailRegex)).toBeTruthy();
expect('user@yahoo.com'.match(emailRegex)).toBeTruthy();
expect('user@hotmail.com'.match(emailRegex)).toBeTruthy();
expect('user@outlook.com'.match(emailRegex)).toBeTruthy();
});
test('invalid emails', () => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@(gmail\.com|yahoo\.com|hotmail\.com|outlook\.com)$/;
expect('user@other.com'.match(emailRegex)).toBeNull();
expect('user@gmail'.match(emailRegex)).toBeNull();
expect('user@.com'.match(emailRegex)).toBeNull();
expect('user@com'.match(emailRegex)).toBeNull();
});
});
Ok, we got some tests written, but I'm pretty sure a lot of tests are missing here, let's see if the AI assistant can help us with that:
I said all possible permutations, please add tests to cover all possible scenarios
describe('emailRegex', () => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@(gmail\.com|yahoo\.com|hotmail\.com|outlook\.com)$/;
test('valid emails', () => {
expect('user@gmail.com'.match(emailRegex)).toBeTruthy();
expect('user@yahoo.com'.match(emailRegex)).toBeTruthy();
expect('user@hotmail.com'.match(emailRegex)).toBeTruthy();
expect('user@outlook.com'.match(emailRegex)).toBeTruthy();
expect('user.name@gmail.com'.match(emailRegex)).toBeTruthy();
expect('user_name@yahoo.com'.match(emailRegex)).toBeTruthy();
expect('user-name@hotmail.com'.match(emailRegex)).toBeTruthy();
expect('user+name@outlook.com'.match(emailRegex)).toBeTruthy();
});
test('invalid emails', () => {
expect('user@other.com'.match(emailRegex)).toBeNull();
expect('user@gmail'.match(emailRegex)).toBeNull();
expect('user@.com'.match(emailRegex)).toBeNull();
expect('user@com'.match(emailRegex)).toBeNull();
expect('user@GMAIL.com'.match(emailRegex)).toBeNull();
expect('user@Yahoo.Com'.match(emailRegex)).toBeNull();
expect('user@hotmail.COM'.match(emailRegex)).toBeNull();
expect('user@Outlook.Com'.match(emailRegex)).toBeNull();
expect('user@outlookcom'.match(emailRegex)).toBeNull();
expect('user@.gmail.com'.match(emailRegex)).toBeNull();
expect('user@gmail..com'.match(emailRegex)).toBeNull();
expect('@gmail.com'.match(emailRegex)).toBeNull();
expect('user@'.match(emailRegex)).toBeNull();
expect('@'.match(emailRegex)).toBeNull();
expect(''.match(emailRegex)).toBeNull();
});
});
I think I want to have these tests divided up a bit for better oversight:
please divide it up into more tests
Now we're talking :)
describe('emailRegex', () => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@(gmail\.com|yahoo\.com|hotmail\.com|outlook\.com)$/;
test('valid emails', () => {
expect('user@gmail.com'.match(emailRegex)).toBeTruthy();
expect('user@yahoo.com'.match(emailRegex)).toBeTruthy();
expect('user@hotmail.com'.match(emailRegex)).toBeTruthy();
expect('user@outlook.com'.match(emailRegex)).toBeTruthy();
expect('user.name@gmail.com'.match(emailRegex)).toBeTruthy();
expect('user_name@yahoo.com'.match(emailRegex)).toBeTruthy();
expect('user-name@hotmail.com'.match(emailRegex)).toBeTruthy();
expect('user+name@outlook.com'.match(emailRegex)).toBeTruthy();
});
test('invalid emails - wrong domain', () => {
expect('user@other.com'.match(emailRegex)).toBeNull();
expect('user@gmail'.match(emailRegex)).toBeNull();
expect('user@com'.match(emailRegex)).toBeNull();
});
test('invalid emails - wrong format', () => {
expect('user@.com'.match(emailRegex)).toBeNull();
expect('user@GMAIL.com'.match(emailRegex)).toBeNull();
expect('user@Yahoo.Com'.match(emailRegex)).toBeNull();
expect('user@hotmail.COM'.match(emailRegex)).toBeNull();
expect('user@Outlook.Com'.match(emailRegex)).toBeNull();
expect('user@outlookcom'.match(emailRegex)).toBeNull();
expect('user@.gmail.com'.match(emailRegex)).toBeNull();
expect('user@gmail..com'.match(emailRegex)).toBeNull();
});
test('invalid emails - missing parts', () => {
expect('@gmail.com'.match(emailRegex)).toBeNull();
expect('user@'.match(emailRegex)).toBeNull();
expect('@'.match(emailRegex)).toBeNull();
expect(''.match(emailRegex)).toBeNull();
});
});
Can we be sure that the RegEx is correct? Well, no, but we definitely have a bunch of tests which is good start. AND, we didn't write the RegEx ourselves, or update it which is even better :)
Jokes aside, we should understand the subject matter, at least on basic level, an AI assistant helps the best when you know your topic and you know what you want to do, but you don't want to do it manually.
References
Summary
This was a bit lengthy newsletter, but I hope you got some ideas on how to use AI assistants with source code. Don't worry I will definitely write more about this topic in the future.
Let's recap all the learnings:
Good luck out there :)
Please comment on this newsletter, I would love to hear your thoughts. Happy to take ideas for future newsletters as well.
What's next?
This was a bit code heavy, but for the next issue, let's talk about presentations, how can an AI assistant help you with that? Everyone from CEO to developer needs to do presentations sooner or later, but let's use an AI assistant to help us with that.
Best Chris
Developer Advocate| Author | Microsoft AI-900 certified | Working on developer education, developer experience, documentation and GenAI.
11moLoved it! Thank you for sharing it!