Jaguar

representacion grafica realizada por Dalle de un robot asistente de chatGPT
imagen de un articulo
Jaguar Ek
Article
5min.

Abusando del ChatGPT para refactorizar un código legacy a TypeScript en un paradigma OOP

Empezar un proyecto nuevo es una de las cosas más emocionantes que podemos hacer como desarrolladores, en este caso tratamos de actualizar un codigo escrito en es5 a typescript, para ello usaremos chatGPT para que nos ayude en el proceso de refactorización.

Empezamos por darle el contexto de que codigo queremos refactorizar, el cual en este caso es la libreria SCORM utilizada por los LMS (Learning Management System) para guardar el progreso de los estudiantes en los cursos.

Primer prompt de chatGPT

En este caso optamos por ingles para aumentar la posibilidad de respuesta en codigo y no tener que traducirlo, el prompt que usamos fue el siguiente:

help me pass this code to class typescript
(function (root, factory) {
  "use strict";
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof module === "object" && module.exports) {
    module.exports = factory();
  } else {
    root.pipwerks = factory();
  }
})(this, function () {
  "use strict";
  var pipwerks = {}; //pipwerks 'namespace' helps ensure no conflicts with possible other "SCORM" variables
  pipwerks.UTILS = {}; //For holding UTILS functions
  pipwerks.debug = { isActive: true }; //Enable (true) or disable (false) for debug mode

   /* ------------------------------------ */
   /* Rest of the Code was pass to ChatGPT */
  /* ------------------------------------ */

La Respuesta de chatGPT

To convert this code to a class in TypeScript, you will need to do the following:

  • Replace the function declaration (function (root, factory) { with a class declaration class Scorm {.
  • Add a constructor method to the class that initializes the class properties, such as pipwerks, debug, SCORM, UTILS, etc.
  • Replace all references to pipwerks and SCORM with this, so that they refer to the class properties.
  • Replace the function calls pipwerks.SCORM.isAvailable() and pipwerks.SCORM.API.get() with class methods isAvailable() and getAPI() respectively.
  • Replace the function call pipwerks.UTILS.trace() with a class method trace()
class Scorm {
    pipwerks = {};
    UTILS = {};
    debug = { isActive: true };
    SCORM = {
        //Define the SCORM object
        version: null, //Store SCORM version.
        handleCompletionStatus: true, //Whether or not the wrapper should automatically handle the initial completion status
        handleExitMode: true, //Whether or not the wrapper should automatically handle the exit mode
        API: {
            handle: null,
            isFound: false,
        }, //Create API child object
        connection: { isActive: false }, //Create connection child object
        data: {
            completionStatus: null,
            exitStatus: null,
        }, //Create data child object
        debug: {}, //Create debug child object
    };
    constructor() { }
    isAvailable() {
        return true;
    }
    trace(msg) {
        if (this.debug.isActive) {
            console.log(msg);
        }
    }
    getAPI() {
        let API = null,
            win = window,
            scorm = this.SCORM,
            find = scorm.API.find,
        ...
    }
}

Aplicamos las recomendaciones que nos dio chatGPT para convertir el codigo es5 a una nueva class en typescript despues de iterar un buen rato creando la definicion de la class, sus metodos y propiedades, le notifique a chatGPT que ya tenia la class en typescript y que me ayudara a mejorarla en base a la nueva definicion.

Promt: chatGPT
I have this class definition help me improve it 😉
export class APIWrapper {
  public sessionLogs: Array<LOG> = [];

  private LMS_INITIALIZED: boolean = false;
  private API: SCORMAPI = null;

  public unset(): void {
    this.LMS_INITIALIZED = false;
    this.API = null;
  }

  public configure(): void {
    this.API = findAPI(window);
    if (this.API == null && window.opener != null && typeof window.opener !== "undefined") {
      this.API = findAPI(window.opener);
    }

    if (this.API == null) {
      throw new Error("A valid SCORM API Adapter can not be found in the window or in the window.opener");
    }
  }

  public isConfigured(): boolean {
    return !(this.API == null);
  }

  public isInitialized(): boolean {
    return this.LMS_INITIALIZED;
  }

  public lmsInitialize(): void {
    // see 3.2.2.1 LMSInitialize
    const operationName: string = "LMSInitialize";
    this.LMS_INITIALIZED = this.isSucceeded(this.API.LMSInitialize(""));
    this.logOperation(operationName);
    if (!this.isInitialized()) {
      this.abort(operationName);
    }
  }

  public lmsCommit(): void {
    const operationName: string = "LMSCommit";
    this.logOperation(operationName);
    if (!this.isSucceeded(this.API.LMSCommit(""))) {
      this.abort(operationName);
    }
  }

  public lmsFinish(): void {
    const operationName: string = "LMSFinish";
    this.logOperation(operationName);
    if (!this.isSucceeded(this.API.LMSFinish(""))) {
      this.abort(operationName);
    }

    this.unset();
  }

  public setScormValue(parameter: string, value: string): void {
    this.logOperation("LMSSetValue", { parameter: parameter, value: value });
    if (!this.isSucceeded(this.API.LMSSetValue(parameter, value))) {
      this.abort("LMSSetValue");
    }
  }

  public getScormValue(parameter: string): string {
    const value: string = this.API.LMSGetValue(parameter);
    this.logOperation("LMSGetValue", { parameter: parameter, value: value });
    return value;
  }

  // A convenience method that do the correct sequence of calls to initialize the communication with the lms
  public initialize(): void {
    this.configure();
    this.lmsInitialize();
  }

  // A convenience method with a more
  public commit(): void {
    this.lmsCommit();
  }

  // A convenience method that do the correct sequence of calls to close the communication with the lms
  public finish(): void {
    this.lmsCommit();
    this.lmsFinish();
  }

  public abort(action: string): void {
    this.LMS_INITIALIZED = false;
    this.API = null;

    throw new Error(`${action} failed`);
  }

  public getLastError(): ERROR {
    const error: string = this.API.LMSGetLastError();

    return {
      code: parseInt(error, 10),
      message: STATUS_CODE[error],
    };
  }

  public logOperation(scormAPIFn: string, scormAPIFnArguments?: any): void {
    const scormLastErrCode: string = this.API.LMSGetLastError();
    this.sessionLogs.push({
      timestamp: Date.now(),
      scormFn: scormAPIFn,
      scormFnArgs: scormAPIFnArguments,
      errorCode: scormLastErrCode,
      errorCodeString: STATUS_CODE[scormLastErrCode],
      errorCodeStringLMS: this.API.LMSGetErrorString(scormLastErrCode),
      diagnostic: this.API.LMSGetDiagnostic(scormLastErrCode),
    });
  }

  private isSucceeded(apiCall: any): boolean {
    return "true" === String(apiCall);
  }
}

export interface ERROR {
  code: number;
  message: string;
}

export interface LOG {
  timestamp: number;
  scormFn: string;
  scormFnArgs: any;
  errorCode: string;
  errorCodeString: string;
  errorCodeStringLMS: string;
  diagnostic: string;
}

Mejoras recomendadas por chatGPT

Basicamente me recomendo que mejorara la definicion de la class, sus metodos y propiedades, para que sea mas legible y facil de entender. Usar try y catch para manejar los errores, y usar enum en lugar de object para definir los codigos de error y sus mensajes.

Aqui esta la lista de las mejoras que me recomendo para mejorar la definicion de la class:

chatGPT response:
Here are a few suggestions to improve this class:
  • Extract the findAPI method into a separate utility function or import it from a different module, so that the class definition is not cluttered with implementation details.
  • Consider making the class properties private if they should not be accessed directly from outside the class.
  • Use the this.API.LMSGetLastError() method to check for errors before returning a value from a function, instead of using this.isSucceeded(). This will simplify the class and make it more readable.
  • Use the this.API.LMSGetErrorString(this.API.LMSGetLastError()) method to get the error message string for the last error occurred, instead of using STATUS_CODE[error].
  • Return the error message string from getLastError() function instead of the error code.
  • Use the enum instead of the object ERROR to define the error codes and messages.
  • Consider using the try-catch block in the lmsInitialize, lmsCommit(), lmsFinish(), setScormValue(), getScormValue(), abort() and getLastError() methods, to handle errors more gracefully and provide more meaningful error messages.
  • Consider using the getter and setter methods to access the properties of the class, instead of directly accessing them.
  • Add type annotations to the class properties and method arguments and return types.
  • Use interface instead of type for the SCORMAPI and LOG types.

Y de hecho hasta un codigo de ejemplo me dio para que lo implemente en mi class; el cual no me parecio necesario incluir en el post ya que lo llenariamos de muchisimos bloques de codigo 😂.

Type Narrowing en TypeScript por chatGPT

Le solicite a chatGPT que me ayudara a escribir un codigo que me permitiera hacer un type narrowing en TypeScript, ya que no sabia como hacerlo.

help write the type narrowing for the function setState()
with and interface using the correct values and params
of cmi SCORM and implementing generics

No era el mejor Promt de la historia 🤷‍♂️, pero basto para que me diera una idea de lo que tenia que hacer.

chatGPT response:
To write the setState() function with type narrowing using an interface
and generics, you could do something like this:
interface CMI {
  completion_status: 'completed' | 'incomplete' | 'not attempted';
  exit: 'time-out' | 'suspend' | 'logout' | 'normal';
  ...
}

class APIWrapper {
  // ... other properties and methods

  public setState<K extends keyof CMI>(key: K, value: CMI[K]): void {
    this.API.LMSSetValue(key, value);
    this.logOperation('LMSSetValue', { 'parameter': key, 'value': value });
  }
}

In this example, the setState() function takes in two parameters,
the first being a key of type K which is a generic type and extends
keyof CMI. This means that K can be any string that exists as a key
in the CMI interface. The second parameter is the value of type CMI[K],
which means that the type of value will be determined by the type
of the key. For example, if the key is 'completion_status',
then the value will be of type 'completed' | 'incomplete' |
'not attempted'. If the key is 'exit', then the value will be
of type 'time-out' | 'suspend' | 'logout' | 'normal'.

¿La inteligencia artificial nos quitara el trabajo a los programadores?

La verdad no lo creo; ya que la inteligencia artificial es solo una herramienta más que tenemos a nuestra disposicion, si alguna persona sin conocimientos de progración, intentara solo con chatGPT refactorizar el codigo, la verdad dudo que llegara tan lejos. Y que chatGPT le diera la implementacion completa como tal y que funcionara.

Extremadamente util cundo tu posees el conocimiento y le das el contexto correcto y preciso a chatGPT, lo cual te genera respuestas mas concretas y "precisas".

En conclusión

Es extremadamente util para realizar este tipo de tareas, por lo menos en mi prespectiva personal, ya que me dio un punto concreto de partida y despues de completar ese punto itere con chatGPT para mejorar mi implementation. Hasta el punto de agregar cosas como type safe narrowing, que no sabia que existia.