ChatGPT Test Drive 1: AuthenticationService SignOut method implementation
How can you use ChatGPT to iteratively implement SignOut method in a networking service? Step by step prompts of how I used ChatGPT to make it refactor code it generated into a decent production code.
Ok, so I've tried ChatGPT on a TypeScript project I'm working on. At first I wanted to write a forgot password method in my authentication service but then decided to go for implementing sign out method. This tests a realistic scenario of how you'd work on your codebase day to day.
Overall I'm impressed with the results. I took an iterative approach, the way I would normally write code, but with ChatGPT obviously.
But at the same time it was tedious and annoying to write all the refactoring commands step by step in plain English where I could do all of those actions myself faster using existing compiler and IDE tools.
Here are the commands/prompts I gave it step by step with my commentary on what I was trying to achieve in each step:
Step 1 - Setup The Context and Ask To Add ForgotPassword Method
Initially I fed it my context, my "codebase", which is in this case just one file with AuthenticationService class implementation, a namespace for URL definitions, and a Session model. All real production code, the way I normally write it.
The initial source code for AuthenticationService:
import { AxiosInstance } from "axios";
import RxAxios from 'axios-observable';
import { delay, map, Observable, of, Subscription } from "rxjs";
export class Session {
constructor(
readonly accessToken: string,
readonly client: string,
readonly uid: string,
readonly expiry: string
) { }
}
namespace Authentication {
export enum API {
V1 = "api/v1/",
Auth = "auth/",
sign_in = "sign_in"
}
}
// /api/v1/auth/sign_in
export interface AuthenticationServiceInterface {
signIn(email: string, password: string): Observable<Session>
}
export class AuthenticationService implements AuthenticationServiceInterface {
constructor(private readonly httpClient: AxiosInstance,
private readonly rxHttpClient: RxAxios) {
}
signIn(email: string, password: string): Observable<Session> {
const signInURL = Authentication.API.V1 + Authentication.API.Auth + Authentication.API.sign_in// + ".json"
console.log(`signInURL: ${signInURL}`);
return this.rxHttpClient.post<string>(signInURL, { email: email, password: password }, {
// return this.rxHttpClient.post<string>(signInURL, { user: { email: email, password: password } }, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
}).pipe(map(response => {
console.log(response);
const accessToken = response.headers["access-token"]
const client = response.headers["client"]
const uid = response.headers["uid"]
const expiry = response.headers["expiry"]
return new Session(accessToken, client, uid, expiry)
}))
}
}
Notice the original implementation, there are already good practices built-in. There is dependency injection (one of the dependencies is not needed btw, chatgpt couldn't find it in my followup prompts), namespace extracted, and a Session class to define the model/data layer for it to refer to (not sure yet if it would've worked as well without it in the initial prompt).
Step 2 - Prompt To Add ForgotPassword Method
This step technically was part of the initial prompt but nevertheless I consider it to be a standalone command/prompt.
This returned a pretty good result following the same pattern as the original `signIn(email: string, password: string): Observable<Session>` method implementation.
It added a forgotPassword method declaration to the public interface and then added an implementation. It looked alright except the unnecessary .pipe(delay(1000)) at the end.
Step 3 - Prompt To Add SignOut Method
Next, I decided to "pivot" and to add another method instead, signOut, to see how it will hold context and to "simulate" a real world scenario of jumping from one thing onto another as you pair program or work on coding something yourself.
This was a good code generation as well. Typically the most conventional way of implementing sign out endpoint would be to make it an HTTP DELETE which is how it implemented it exactly.
Also, notice, chatgpt followed the "pattern" of the context given and mimicked implementation of signOut method exactly how signIn method looked:
create the URL by composing it of namespace strings
console.log the url
create and return HTTP POST RxAxios observable
add header params to it
But now notice the issues with the implementation:
last "sign_out" part of the url wasn't put into the namespace Authentication enum API and instead is a hardcoded string*
accessToken, client, and uid refer to non-existing instance properties
the console.log was preserved
Step 4 - Prompt To Refactor SignOut Method To Take Params For uid, client, and accessToken
The first glaringly obvious issue that I prompted it to refactor was the non-existing instance properties. There are different ways this could've been handled - I think in the long run I'll refactor this implementation to use a session manager dependency, but for now the easiest solution is to pass those properties as method params.
AuthenticationManager doesn't save or manage session (i.e. accessToken, client, uid) data on its own in signIn method nor should it in signOut.
The result is pretty good again, it "knows" the context so it's pretty straightforward for it to convert properties to params passed in signOut method. No complaints here, exactly how I would do it. Although, I wouldn't be referring to these pieces of data as properties in the first place.
Step 5 - Prompt To Extract "sign_out" hardcoded part of url string into namespace
Next was small cleanup. It did it as expected. Not sure why it added a "New constant" comment at the end though. Probably it was easy because it had "context" and a "pattern" of existing namespace where to put these strings. Not sure it would create one for you from scratch.
Here I can't say that this refactoring was unnecessary or that I wouldn't hardcode it myself if I was coding it. I would because it's natural to add hardcoded strings on your first iteration and then refactor to put them into constants.
Step 6 - Prompt To Remove console.log
Next was a prompt to cleanup console.logs. And it did remove it from the entire file, both signIn and signOut methods.
No complains here, I'd do similar kind of cleanup myself just using IDE search and replace tool.
Step 7 - Prompt To Remove forgotPassword Method
The last one was a command to cleanup the code and remove forgotPassword method that is not needed anymore. It cleaned it up and the good thing was that it not only removed the method implementation but also the interface declaration of it.
The Result
import { AxiosInstance } from "axios";
import RxAxios from 'axios-observable';
import { delay, map, Observable, of, Subscription } from "rxjs";
export class Session {
constructor(
readonly accessToken: string,
readonly client: string,
readonly uid: string,
readonly expiry: string
) { }
}
namespace Authentication {
export enum API {
V1 = "api/v1/",
Auth = "auth/",
sign_in = "sign_in",
sign_out = "sign_out"
}
}
export interface AuthenticationServiceInterface {
signIn(email: string, password: string): Observable<Session>
signOut(uid: string, client: string, accessToken: string): Observable<any>
}
export class AuthenticationService implements AuthenticationServiceInterface {
constructor(private readonly rxHttpClient: RxAxios) {
}
signIn(email: string, password: string): Observable<Session> {
const signInURL = Authentication.API.V1 + Authentication.API.Auth + Authentication.API.sign_in// + ".json"
const something = "123"
// return this.rxHttpClient.post<string>(signInURL, { email: email, password: password }, {
return this.rxHttpClient.post<string>(signInURL, { email, something }, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
}).pipe(map(response => {
console.log(response);
const accessToken = response.headers["access-token"]
const client = response.headers["client"]
const uid = response.headers["uid"]
const expiry = response.headers["expiry"]
return new Session(accessToken, client, uid, expiry)
}))
}
signOut(uid: string, client: string, accessToken: string): Observable<any> {
const signOutURL =
Authentication.API.V1 + Authentication.API.Auth + Authentication.API.sign_out
return this.rxHttpClient.delete(signOutURL, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"access-token": accessToken,
client: client,
uid: uid,
},
})
}
}
Ultimately, I got the result I wanted refactoring my way into it almost the same way I'd do it myself. This code will go into production.
All in all, even though I'm a skeptic, I'm impressed by the result. But as of today I could do all of this coding and refactoring myself way faster with a help of a compiler and IDE.
It was so annoying and tedious to keep asking the machine (chatgpt) to perform refactoring actions in plain English. Normally I'd do it with keyboard commands/shortcuts instead. I suppose it is somewhat akin to a pair programming session when you're asking your pair to do stuff in the code and not driving the session yourself. But in this case your "pair" has very limited context and understanding of what you're working on but great breadth of referential knowledge about libraries and languages.
P.S. only during writing this article I realized that the chatgpt generated code (and myself) missed the `expiry` header param that is supposed to be attached to sign out request. Another example that you should "trust but verify".
P.P.S. sorry Swift/iOS folks, this issue is with Typescript code but I’d make exactly the same implementation in Swift so the same logic and approach applies.
I have used ChatGPT for code generation and usually you have to guide it several times before it gives the outcome you are looking for. Nevertheless I still find it useful as it definitely helps save time in having to write all those lines of code from scratch and debug over and over again. I am not a coder by profession but the times I have used chatGPT, it has not been a bad experience, rather a learning curve in understanding how to query it and provide examples of sample code to ChatGPT so that it understands what exactly you are asking for.