생각을 자동화하는 도구, ChatGPT

ilotoki·2023년 7월 29일
0
post-thumbnail

개요

프로그래밍을 하는 이유 중에 하나는 반복적인 작업을 자동화하기 위해서이다. 예를 들어 스크래핑의 경우에는 프로그래밍 대신 이미지를 드래그 가능하게 한 뒤 모두 저장하면 '직접' 스크래핑을 할 수 있다. 하지만 이는 굉장히 반복적이고 지루하며 시간집약적인 작업이다. 이를 프로그래밍을 이용하면 자동화할 수 있을 뿐만 아니라 훨씬 더 빠르게 작업이 가능하다.
그렇다면 반복적인 '생각'을 자동화하기 위한 도구는 무엇일까? 나는 이것이 바로 ChatGPT라고 생각한다. 다음은 내가 스크래퍼를 만들기 위해 ChatGPT를 활용한 예를 소개하겠다.

ChatGPT를 통한 생각의 자동화 사례

나는 레진코믹스라는 웹사이트를 분석해야 할 일이 생겼다. 그중에서도 내가 원하는 핵심적인 기능이 어떤 함수에 존재한다는 사실을 알 수 있었다.

func = function(t) {
    var l = this
    , f = this.$scrollList.find("[data-cut-index=" + t + "]")
    , h = this.scrollCuts[t - 1];
    if (!f.find("canvas,img").length) {
        f.dataset("cut-status", "loading"),
        f.find(".cut-status").length || f.append($("#status-template").html());
        var e = this.buildUrl("scroll", t, f);
        Object(st.a)(e).then(function(t) {
            f.find(".cut-status").remove();
            var e, n, r, i, o, a, s, u = /top|presub|bottom/.test(h.type || "") || h.mediaType.includes("/gif");
            if (l.hasShuffle) {
                var c = u ? t : Ae(t, l.episode.id, f);
                l.hasTrace ? f.append(u ? t : (e = h,
                n = c,
                r = l.episode.trace.markId,
                a = n.width / 720,
                s = n.getContext("2d", {
                    alpha: !1
                }),
                e.mark && (i = s,
                o = e.mark,
                i.textBaseline = "top",
                o.forEach(function(t) {
                    i.fillStyle = t.color,
                    i.font = "".concat(Math.floor(t.size * a), "px sans-serif"),
                    i.fillText(r, Math.floor(t.x * a), Math.floor(t.y * a))
                })),
                n)) : f.append(c),
                t = null
            } else
                l.hasTrace ? f.append(u ? t : function(t, e, n) {
                    var r = e.naturalWidth
                      , i = e.naturalHeight
                      , o = document.createElement("canvas");
                    o.width = r,
                    o.height = i;
                    var a, s, u = r / 720, c = o.getContext("2d", {
                        alpha: !1
                    });
                    return c.drawImage(e, 0, 0, o.width, o.height),
                    t.mark && (a = c,
                    s = t.mark,
                    a.textBaseline = "top",
                    s.forEach(function(t) {
                        a.fillStyle = t.color,
                        a.font = "".concat(Math.floor(t.size * u), "px sans-serif"),
                        a.fillText(n, Math.floor(t.x * u), Math.floor(t.y * u))
                    })),
                    o
                }(h, t, l.episode.trace.markId)) : (t.alt = "",
                t.draggable = !1,
                f.append(t));
            f.dataset("cut-status", "success")
        })["catch"](function(t) {
            console.log(t),
            f.dataset("cut-status", "fail")
        })
    }
}

코드를 조금만 봐도 어딘가 이상하다는 느낌 – 적어도 원본이 아닐 것이라는 느낌 – 을 받을 수 있을 것이다. 코드를 보면 변수 이름이 죄다 한 글자라는 것이다. 처음에는 어느 정신 나간 사람이 이렇게 코드를 짜나 싶었지만 이내 이가 JSMin이라는 도구로 줄여진 상태라는 것을 알 수 있었다.

JSMin은 자바스크립트 코드 크기를 줄여 다운로드 시간을 줄이고 더욱 빠르게 코드를 실행할 수 있도록 하는 도구이다. 이 과정에서 변수 이름을 한 자 내지 두 자로 줄인다. 변수의 이름으로 받을 수 있는 힌트가 전혀 없으니 이해하기가 상당히 까다로워진다.
하지만 JSMin은 하는 일을 숨기진 않는다. 기존 코드와 동작은 완전히 같게 한다는 것이다.

그렇다면 이것을 어떻게 읽기 쉽게 할 수 있을까? 가장 먼저 떠올릴 수 있는 방법은 직접 코드가 하는 일을 읽으면서 해석해보고 추측에 어울리는 이름으로 이름을 직접 변경하는 것이다. 어차피 변수의 이름은 에디터의 기능을 이용하면 몇 번의 조작만으로 변경할 수 있지만 변수의 개수가 꽤 되기 때문에 굉장히 '반복적'인 작업이 될 뿐만 아니라 코드를 완전히 이해해야 했다. 나는 파이썬 개발자지 자바스크립트 개발자는 아닐 뿐더러 코드는 이미 minified 상태이니 상당히 어려운 작업이 될 것이다.

이때 사용할 수 있는 것이 바로 ChatGPT이다. 나는 ChatGPT에게 몇 번의 시행착오를 통해 가장 잘 답을 생성하는 프롬프트를 알아냈고 이를 통해 위의 코드를 unminified하는 데에 성공했다.

프롬프트: This data is minified by JSmin, and I want to deaminizing this data. Please change variable or function name to human-readable like f.append('hello') to worldGreetingWords.append('hello'). You can change the implementation of code aggressively; like condition ? true_expression : false_expression to if (condition) \n true_expression \n else \n false_expression. AND YOU HAVE TO CHANGE EVERY NAME THAT LANGTH IS ONE OR TWO TO DISCRIPTIVE NAME.
해석본 (by ChatGPT): 이 데이터는 JSmin에 의해 최소화되었으며, f.append(‘hello’)와 같은 변수 또는 함수 이름을 worldGreetingWords.append(‘hello’)와 같이 사람이 읽을 수 있는 이름으로 변경하고 싶습니다. 조건 ? true_expression : false_expression과 같은 코드 구현을 if (condition) \n true_expression \n else \n false_expression으로 과감하게 변경할 수 있습니다. 그리고 길이가 1 또는 2인 모든 이름을 설명되는 이름으로 변경해야합니다.

그 결과는 다음과 같다.

function processImage(t, imageIndex, imageElement) {
    var shuffleMatrix = generateShuffleMatrix(imageIndex, 5);
    
    if (utils.size(shuffleMatrix) < 4 || !t || imageElement.hasClass("cutLicense"))
        return null;

    var preparedCanvasData = prepareCanvas(t);

    utils.pipe(shuffleMatrixProcessing, (sourceImage = preparedCanvasData.image,
                                        canvas = preparedCanvasData.canvas,
                                        sourceDimensions = {
                                            width: sourceImage.naturalWidth,
                                            height: sourceImage.naturalHeight
                                        },
                                        destinationDimensions = {
                                            width: canvas.width,
                                            height: canvas.height
                                        },
                                        function(matrix) {
                                            var sideItems = Math.floor(Math.sqrt(matrix.length));
                                            
                                            return utils.pipe(utils.entries, utils.map(function(matrixEntry) {
                                                var fromLocation = calculateClusterPosition(sourceDimensions, sideItems, utils.toNumber(matrixEntry[0]));
                                                var toLocation = calculateClusterPosition(destinationDimensions, sideItems, matrixEntry[1]);
                                                
                                                return {
                                                    from: fromLocation,
                                                    to: toLocation
                                                };
                                            }), utils.filter(function(matrixEntry) {
                                                var fromLocation = matrixEntry.from;
                                                var toLocation = matrixEntry.to;
                                                
                                                return !!fromLocation && !!toLocation;
                                            }))(matrix);
                                        }
                                       ), (sourceImage = preparedCanvasData.image,
                                           canvasContext = preparedCanvasData.canvas.getContext("2d", {
                                               alpha: !1
                                           }),
                                           utils.each(function(entry) {
                                               var fromLocation = entry.from;
                                               var toLocation = entry.to;
                                            
                                               return canvasContext.drawImage(sourceImage, toLocation.left, toLocation.top, toLocation.width, toLocation.height, fromLocation.left, fromLocation.top, fromLocation.width, fromLocation.height);
                                           })))(shuffleMatrix);

    return preparedCanvasData.canvas;
}

function generateShuffleMatrix(matrix, numberOfItems) {
    return [].concat(getElements(matrix), [matrix.length, matrix.length + 1]);
}

function getElements(matrix) {
    if (Array.isArray(matrix)) {
        return getElementIndex(matrix);
    } else if (isIterable(matrix)) {
        return Array.from(matrix);
    } else if (!isIterable(matrix)) {
        throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }
}

function isIterable(matrix) {
    return "undefined" != typeof Symbol && null != matrix[Symbol.iterator] || null != matrix["@@iterator"];
}

function prepareCanvas(image) {
    var canvas = document.createElement("canvas");
    canvas.width = image.naturalWidth;
    canvas.height = image.naturalHeight;

    var dataIndex = image.getAttribute("data-index");
    var dataType = image.getAttribute("data-type");

    canvas.setAttribute("data-index", dataIndex);
    canvas.setAttribute("data-type", dataType);

    return {
        image: image,
        canvas: canvas
    };
}

function calculateClusterPosition(dimension, sideItems, entry) {
    // Implement the cluster position calculation logic
}

function shuffleMatrixProcessing(matrixProcessingFunction, preparedCanvasData, shuffleMatrix) {
    // Implement the logic for processing the matrix
}

ChatGPT가 unminified해준 코드를 보니 calculateClusterPositionshuffleMatrixProcessing에 해당하는 코드를 내가 전해주지 않았다는 것을 알 수 있었다. 이 해당하는 코드도 ChatGPT가 해석할 수 있도록 전해 주었다.

class OrderGenerator {
    constructor(episodeId, gridSize) {
        // Ensure the class is instantiated properly
        if (!(this instanceof OrderGenerator)) {
            throw new TypeError("Cannot call a class as a function");
        }

        // Assign functions as class members
        this.randomNumberWithinRange = (maxValue) => {
            let state = this.state;
            state = state.xor(state.shiftRight(Se()(12)));
            state = state.xor(state.shiftLeft(Se()(25)).and(Se()("18446744073709551615")));
            state = state.xor(state.shiftRight(Se()(27)));
            this.state = state.and(Se()("18446744073709551615"));
            return state.shiftRight(Se()(32)).remainder(Se()(maxValue)).toJSNumber();
        };

        this.getOrder = () => {
            return this.order;
        };

        // Initialize class properties
        this.seed = episodeId;
        this.state = new BigNumber(this.seed);

        // Generate order array
        let totalSize = gridSize * gridSize;
        let arrayValues = Array.from({ length: totalSize }, (value, index) => index);
        for (let i = 0; i < arrayValues.length; i++) {
            let randomPosition = this.randomNumberWithinRange(i);
            let currentValue = arrayValues[i];
            arrayValues[i] = arrayValues[randomPosition];
            arrayValues[randomPosition] = currentValue;
        }
        this.order = arrayValues;
    }
}
function calculateImageSection(image, gridSize, index) {
    var gridWidth, gridHeight, imgWidth, imgHeight, sectionWidth, sectionHeight, sectionLeft, sectionTop, totalGridItems = gridSize * gridSize;

    if (index < totalGridItems) {
        imgWidth = image.width;
        imgHeight = image.height;
        
        sectionWidth = Math.floor(imgWidth / gridSize);
        sectionHeight = Math.floor(imgHeight / gridSize);
        
        sectionLeft = index % gridSize * sectionWidth;
        sectionTop = Math.floor(index / gridSize) * sectionHeight;

        return {
            left: sectionLeft,
            top: sectionTop,
            width: sectionWidth,
            height: sectionHeight
        };

    } else if (index === totalGridItems) {
        imgWidth = image.width;
        imgHeight = image.height;

        sectionLeft = imgWidth - (imgWidth % gridSize);
        sectionTop = 0;
        sectionWidth = imgWidth % gridSize;
        sectionHeight = imgHeight;

        return (sectionWidth === 0) ? null : {
            left: sectionLeft,
            top: sectionTop,
            width: sectionWidth,
            height: sectionHeight
        };

    } else {
        imgWidth = image.width;
        imgHeight = image.height;

        sectionLeft = 0;
        sectionTop = imgHeight - (imgHeight % gridSize);
        sectionWidth = imgWidth - (imgWidth % gridSize);
        sectionHeight = imgHeight % gridSize;

        return (sectionHeight === 0) ? null : {
            left: sectionLeft,
            top: sectionTop,
            width: sectionWidth,
            height: sectionHeight
        };
    }
}

ChatGPT가 해석해준 두 코드를 보고 나는 성공적으로 코드를 분석해낼 수 있었다. 완전히 작동하는 코드인지는 내가 알 도리가 없지만 어차피 나는 대략적인 코드의 작동 방식만 참고해서 스크래퍼를 만드는 것이 목표였기 때문에 큰 상관은 없었다. 독자분들도 ChatGPT를 통해 생각의 자동화를 해 생산성을 높일 수 있으면 좋겠다.

2개의 댓글

comment-user-thumbnail
2023년 7월 29일

이런 유용한 정보를 나눠주셔서 감사합니다.

1개의 답글