Refactoring ChatGPT code, and making it our own!
You should do this with any raw ChatGPT code or code in the wild. Here's how...
Recap
On this first post, we explored some boilerplate Java code (util class) to create a Google Calendar event. We explored how the code doesn’t follow design principles, such as DRY, separation of concerns, …
I left it at that we won’t refactor it there, as I just wanted to highlight the code quality issues…
Let’s do that refactoring here…
Design principles
Don’t-Repeat-Yourself (DRY)
We can think of this principle as an application of KISS (Keep It Simple Stupid). We can think of it as keeping everything in its own place, and only there. If you have to repeat an implementation elsewhere in the codebase, you’re doing it wrong!
It was popularized by the book The Pragmatic Programmer. More precisely, in that book, it states that “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
This is where util classes, util methods … come into play.
Separation of Concerns
This is a general design principle that is implemented several different ways. Typically it looks like the DRY principle, abstraction in Object-Oriented-Programming (OOP), singleton design pattern, etc…
Singleton Pattern
This is a common design pattern, for when you want only one of something. This includes loggers, general util classes, library object instances, … .
It is typically applied on the class of which we want only one object, like this:
public class SingletonClass {
private static SingletonClass _instance;
private SingletonClass() {
// implementation here...
}
public static SingletonClass GetInstance() {
if (_instance == null)
_instance = new SingletonClass();
return _instance;
}
}
However, we can use this to protect fields of the class/abstract out their initialization:
public final class MyUtils {
private static SomeLibraryObject _libraryObjectInstance;
public static SomeLibraryObject GetLibraryObjectInstance() {
if (_libraryObjectInstace == null) {
// initialize _libraryObjectInstance here
}
return _libraryObjectInstance;
}
}
Let’s apply this to our util class!
Refactoring time!
If you ask vanilla ChatGPT to do this, it will run out of tokens. Let’s do it ourselves, right now!
Separate the service initialization and event creation concerms…
Our first target is to separate the service creation from the calendar.Event
creation/posting ones. We’re going to address this via the singleton design pattern:
public class GoogleCalendarUtil {
private Calendar _calendarInstance;
// ... rest of static fields ...
/**
* Send a Google Calendar invite to a user email with specified event details.
*
* @param userEmail the email address of the user to whom the invite is sent
* @param eventTitle the title of the event
* @param eventDescription the description of the event
* @param eventStart the start time of the event
* @param eventEnd the end time of the event
* @throws IOException
* @throws GeneralSecurityException
*/
public static void SendGoogleCalendarInvite(String userEmail, String eventTitle, String eventDescription, Date eventStart, Date eventEnd) throws IOException, GeneralSecurityException {
// ...code to create the Calendar event
Event createdEvent = GetCalendarInstance().events()
.insert("primary", event)
.execute();
System.out.printf("Event created: %s\n", createdEvent.getHtmlLink());
}
public static Calendar GetCalendarInstance() {
if (_calendarInstance == null)
_calendarInstance = new Calendar.Builder(GoogleCredential.getTransport(),
GoogleCredential.getJsonFactory(),
authorize())
.setApplicationName(APPLICATION_NAME)
.build();
return _calendarInstance;
}
/**
* Authorize the Google Calendar API client.
*
* @return the authorized credential
* @throws IOException
* @throws GeneralSecurityException
*/
private static Credential authorize() throws IOException, GeneralSecurityException {
return new GoogleCredential.Builder()
.setTransport(GoogleCredential.getTransport())
.setJsonFactory(GoogleCredential.getJsonFactory())
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountPrivateKeyFromP12File(new java.io.File(KEY_FILE_LOCATION))
.setServiceAccountScopes(SCOPES)
.build();
}
}
I did apply my own naming convention to the code: public static
stuff starts with capital letter. This departs from Java convention, but idc… (imo it makes the code more readable)
Helper method for date time creation…
If we look back at our ChatGPT code, you may notice that it does the same three lines to set start/end date of the event:
DateTime startDateTime = new DateTime(eventStart, TimeZone.getTimeZone(TIME_ZONE));
EventDateTime start = new EventDateTime().setDateTime(startDateTime);
event.setStart(start);
DateTime endDateTime = new DateTime(eventEnd, TimeZone.getTimeZone(TIME_ZONE));
EventDateTime end = new EventDateTime().setDateTime(endDateTime);
event.setEnd(end);
We’ll address this by creating a simple helper method:
private static EventDateTime ToEventDateTime(Date date) {
return new EventDateTime()
.setDateTime(new DateTime(date), TimeZone.getTimeZone(TIME_ZONE));
}
This implements the DRY principle.
Separate calendar.Event
creation concern from calendar.Event
posting concern…
If we read the ChatGPT code, we can notice a specific implementation: it has only one EventAttendee
. What if we wanted multiple? We’d have to change the source code for sendGoogleCalendarInvite
just to make that happen!
Let’s separate those concerns…
public static void SendGoogleCalendarInvite(Event event) throws IOException, GeneralSecurityException {
Event createdEvent = GetCalendarInstance().events()
.insert("primary", event)
.execute();
System.out.printf("Event created: %s\n", createdEvent.getHtmlLink());
}
public static Event CreateSingleAttendeeCalendarEvent(String userEmail, String eventTitle, String eventDescription, Date eventStart, Date eventEnd) {
return CreateCalendarEvent(Arrays.asList(userEmail),
eventTitle,
eventDescription,
eventStart,
eventEnd,
);
}
public static Event CreateCalendarEvent(List<String> attendeeEmails, String eventTitle, String eventDescription, Date eventStart, Date eventEnd) {
return new Event()
.setSummary(eventTitle)
.setDescription(eventDescription)
.setStart(ToEventDateTime(start))
.setEnd(ToEventDateTime(end))
.setAttendees(attendeeEmails.stream()
.map(email -> new EventAttendee().setEmail(email))
.collect(Collectors.toList()));
}
With this separation, we can focus on our app’s concerns for creating and sending Google Calendar Events…
… for example, in my Katalon Studio testing code base (Katalon Studio uses the JVM language known as Groovy), I may want to create them to remind myself to run a test case some days after another test case completes.
…Maybe you have an app written in a JVM language, and you want to add Calendar event functionality to it.
…Maybe you want more complex Calendar Events than what the raw ChatGPT code will allow (for example adding an Event location)
This separation of concerns helps facilitate that. Simply create your util method to handle the Calendar Event for your use case. Add as little or as many fields as you need, it should:
create the
calendar.Event
itselfpass that
calendar.Event
toSendGoogleCalendarInvite()
handle failures in that class. For example, you may have your implementation inside a callback-driven retry-loop, such that, on failure, in the event of a
TokenResponseException
, you delete the tokens folder and reset the_calendarInstance
tonull
to trigger a user re-auth. NOTE: This ChatGPT code snippet doesn’t seem to explicitly handle token folders, but the official Google Quickstart for Java does.
Putting it altogether
Our refactored code should now look something like this:
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Date;
import java.util.TimeZone;
import java.util.List;
import java.util.stream.Collectors;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.util.DateTime;
import com.google.api.services.calendar.Calendar;
import com.google.api.services.calendar.CalendarScopes;
import com.google.api.services.calendar.model.Event;
import com.google.api.services.calendar.model.EventAttendee;
import com.google.api.services.calendar.model.EventDateTime;
public class GoogleCalendarUtil {
private static final String APPLICATION_NAME = "Your Application Name";
private static final List<String> SCOPES = Arrays.asList(CalendarScopes.CALENDAR_EVENTS);
private static final String SERVICE_ACCOUNT_EMAIL = "your-service-account-email@your-project-id.iam.gserviceaccount.com";
private static final String KEY_FILE_LOCATION = "path/to/your/key.p12";
private Calendar _calendarInstance;
public static void SendGoogleCalendarInvite(Event event) throws IOException, GeneralSecurityException {
Event createdEvent = GetCalendarInstance().events()
.insert("primary", event)
.execute();
System.out.printf("Event created: %s\n", createdEvent.getHtmlLink());
}
public static Event CreateSingleAttendeeCalendarEvent(String userEmail, String eventTitle, String eventDescription, Date eventStart, Date eventEnd) {
return CreateCalendarEvent(Arrays.asList(userEmail),
eventTitle,
eventDescription,
eventStart,
eventEnd,
);
}
public static Event CreateCalendarEvent(List<String> attendeeEmails, String eventTitle, String eventDescription, Date eventStart, Date eventEnd) {
return new Event()
.setSummary(eventTitle)
.setDescription(eventDescription)
.setStart(ToEventDateTime(start))
.setEnd(ToEventDateTime(end))
.setAttendees(attendeeEmails.stream()
.map(email -> new EventAttendee().setEmail(email))
.collect(Collectors.toList()));
}
private static EventDateTime ToEventDateTime(Date date) {
return new EventDateTime()
.setDateTime(new DateTime(date), TimeZone.getDefault());
}
public static Calendar GetCalendarInstance() {
if (_calendarInstance == null)
_calendarInstance = new Calendar.Builder(GoogleCredential.getTransport(),
GoogleCredential.getJsonFactory(),
authorize())
.setApplicationName(APPLICATION_NAME)
.build();
return _calendarInstance;
}
/**
* Authorize the Google Calendar API client.
*
* @return the authorized credential
* @throws IOException
* @throws GeneralSecurityException
*/
private static Credential authorize() throws IOException, GeneralSecurityException {
return new GoogleCredential.Builder()
.setTransport(GoogleCredential.getTransport())
.setJsonFactory(GoogleCredential.getJsonFactory())
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountPrivateKeyFromP12File(new java.io.File(KEY_FILE_LOCATION))
.setServiceAccountScopes(SCOPES)
.build();
}
}
Some disclaimers
I make no claims to whether or not the ChatGPT code, in its raw or refactored form, works.
As of the writing of this blog post, I haven’t personally tested it (for me, right now, it is icing on the cake).
You may want to further tweak the code I outline here. For example, I may use the List literal
[userEmail]
instead ofArrays.asList(userEmail)
since I am working in Groovy. Also, you may decide thatSendGoogleCalendarEvent()
should return theEvent
it created (logical choice).
Conclusion
With these design principles, we are now ready to take on a lot of code, esepcially any ChatGPT-provided code, and make it our own!