Massage Website Updates
Massage Website Updates AI Rant
As I work on my massage website I figured I’d share some insights into why I do some of the things I do when it comes to building out the website.
AI Has No Sense of DRY
I’ve been using coding agents like Claude, OpenCode, Pi and whatever else tickles my fancy to get the job done. I also heavily review and manually edit a lot of this site and fundamentally disagree with just letting agents run rampant on my website and turn it into slop.
Let’s take an example from some of my recent commits.
diff --git a/shared/massage/types.ts b/shared/massage/types.ts
index 9112b14..ef5198c 100644
--- a/shared/massage/types.ts
+++ b/shared/massage/types.ts
@@ -372,32 +372,6 @@ export interface TherapistClient {
> | null;
}
-/** A booking as the therapist panel shows it. */
-export interface TherapistBooking extends BookingDetails {
- _id: string;
- client: TherapistClient | null;
- soapStatus: SoapStatus;
-}
-
-/** Response of `GET /api/therapist/bookings`. */
-export interface TherapistBookingsResponse {
- success: boolean;
- bookings: TherapistBooking[];
-}
-
-/** Response of `GET /api/therapist/soaps/:bookingId`. */
-export interface SoapNoteResponse {
- success: boolean;
- booking: TherapistBooking;
- soap: SoapNote | null;
-}
-
-/** Response of the SOAP start, save, and no-show endpoints. */
-export interface SoapNoteSavedResponse {
- success: boolean;
- soap: SoapNote;
-}
-
/**
* **Server Information**
* Generic server information data structure contracts.Instead of creating 1000 differently named types in all of the files
locally, I am a fan of centralizing names and functionality and I write
very DRY
code. What the AI chose to do was to take that concept and pollute it
with a bunch of *Response objects defining what each API
response was going to look like.
Notice the use of success as a key in all of them? It
comes from something I used commonly as an inferred type that I didn’t
make explicit indicating a quick success boolean field and
a message field that could be a string or the content of
the response itself.
Instead of honouring this inference, the AI decided to create a unique object for every single type of response. Thus, bloating the code and producing a ridiculous amount of changes that are simply not necessary.
Centralizing Utility
Here’s another example, and this diff has two parts I want to explain:
diff --git a/shared/massage/utils.ts b/shared/massage/utils.ts
index 17af350..3371245 100644
--- a/shared/massage/utils.ts
+++ b/shared/massage/utils.ts
@@ -1,5 +1,56 @@
import { CAL_BOOKING_ACTIONS } from './const';
-import { CalBookingLinkParams } from './types';
+import { Massages } from './sitecfg';
+import { CalBookingLinkParams, MassageDetails, ServerResponse } from './types';
+
+/**
+ * Server success message.
+ */
+export function success<T = string>(message: T, status: number = 200): ServerResponse<T> {
+ return {
+ success: true,
+ status,
+ message,
+ }
+}
+
+/**
+ * Server failure message.
+ */
+export function failure<T = string>(message: T, error?: Error, status: number = 500): ServerResponse<T> {
+ return {
+ success: false,
+ status,
+ message,
+ error,
+ }
+}
+
+/**
+ * Map Massage Type is created by AI too many times in both front and backend.
+ * Unifies the operations into a single function here.
+ * Input potential MassageType and output/returns the Massage Name you can use as a Label.
+ * This is a slightly safer operation to return just the label.
+ * @param {MassageType} type The type of massage provided.
+ * @returns {string} Massage Name from MassageType or empty string.
+ */
+export function mapMassageTypeToName(type: string): string {
+ return Massages.find(m => type === m.type)?.name ?? '';
+}
+
+/**
+ * The above returns just the name for the label. This returns the entire data structure.
+ * Mostly used in instant transformations when iterating over DB results.
+ * @param {MassageType} type The type of massage provided.
+ * @returns {MassageDetails} The details/data structure of the massage.
+ * @throws {RangeError} If value not found. This ensures we get a value.
+ */
+export function mapMassageTypeToMassage(type: string): MassageDetails {
+ const result = Massages.find(m => type === m.type);
+ if ( !result ) {
+ throw new RangeError(`MassageType(${type}) not found.`);
+ }
+ return result;
+}
/**
* Converts a 10-digit US phone number to E.164 format.The first part is the creation of the success() and
failure() methods. Since I was OK inferring the type, I
could easily just return { success: true, message: 'OK'};
in all my API communications code.
An AI can’t infer types like that. So you have to make it explicit.
I created these functions so the AI will stop trying to create
MassageResponse, MassageFailureResponse,
BookingResponse, BookingFailureResponse,
SoapResponse, SoapSuccessResponse,
SoapFailureResponse….. you get the idea …
The thing about these language models, is they will happily generate more code to fill the void of where an instruction leads to their green test and telling you the code was written and the tests pass. They are too helpful to the point of detriment.
Some of my best code was written with my backspace key.
Centralizing Functionality
The second half of this diff points to the massage map functions. I
noticed Claude and Gemini trying to create a unique situation for these
so many times it warranted centralizing in the
@massage/shared/utils. AI won’t think to do this on its
own. You have to tell it. AI will happily repeat the same code 8 ways to
Sunday and you will be none the wiser because you’re too lazy to review
the thing you asked to get done. I refuse to be that lazy. Hence this
exercise.
I will admit, I did let loose on the reigns because I wanted to go a little faster and get a few more features done before the next release. However, now, I am paying the price of not being as meticulous as I normally am when writing code myself. In this course-correction exercise, I’ll eventually have a clearer road to the code I want written in the way I want it written, all the correct tests written that pass in the way they should and testing the things they should.
AI Fallacy
Some people take AI as part of their identity without any clue about opinions, taste and judgement and will try to defend the AI’s actions or attack me for my decisions and lack of instructing the AI to just simply remove this excess of code.
Don’t get me wrong, I will try it again. For now, I am course correcting and part of this exercise is for me to help collect metrics on what matters to me and how I can better measure them, how I can include those in my prompts and where I should provide additional direction.
I have a love-hate relationship with LLMs… sometimes, they astound me and get what I wanted exactly without further instruction. Other times they do stupid stuff like this and it’s an incredible disappointment. I feel like you have to have balance between the two and can’t just go willy nilly in either direction. At least… that’s how I’m navigating all of this.
AI’s Concept of Software
You see, AI has no concept of software architecture, no concept of “this data structure exists elsewhere, so why do I need to create a new one?”. It can’t justify these kinds of changes. All it does is follow orders and do what you say. I prompted Opus to provide me with prompts I could hand to cheaper models and cost me less in tokens. I asked it to ask me questions in order gain a shared alignment and understanding of the task at hand. While it wrote the code, the tests passed and functionally speaking… the site did what I asked it to do.
AI looks at your instructions and tries to map them with the most
likely next token – the next likely code function that does what you
asked it based on inference and it won’t go and review what exists to
leverage that. Similar to novice engineers on a project don’t want to
delete or change existing code because of the “What If” something else
is using it is the reason why it’s all newly generated code and rarely
anything deleted. By the way, checking to see if something is used
elsewhere is as simple as a grep for those that have access
to the entire codebase.
My challenge is when I looked under the hood to find out the “why” behind it and debug what happened, I am appalled at what I found.
There’s repeat code like this all over the application. Nifty techniques where it created an excessive number of data structure definitions that totally bloat the context window of future iterations and add unnecessary lines of code to the project; thus costing me more tokens and requiring heftier models to deal with the bulk of the project and for me to be all the more specific about the request I made.
Future Plans
When I’m done with this [manual] cleanup exercise, I’m going to pass the diffs + commits to Claude and see where it can provide a skill of sorts or whatever to help ensure that future iterations require less of this manual cleanup work; to try and teach it more of my taste and judgement (and opinions) of how I believe software should work. Each line of code should be defensible/justifiable and earn its right to exist in your codebase. Otherwise, it’s just cruft you’ll either have to clean out or will forever just take up space in your bundle, costing you precious bandwidth, processing compute, tokens and mental strain.
This idea of producing so much code that humans can’t review to me is absolutely absurd and sounds like laziness to me, but this is just my opinion and perhaps engineer is too close to my sense of identity as I’ve been in this practice for over 22 years and have strong opinions about how code should be not only efficient, but also elegant and readable among both humans and machines.
Comments
Post a Comment