프로그래밍을 하는 이유 중에 하나는 반복적인 작업을 자동화하기 위해서이다. 예를 들어 스크래핑의 경우에는 프로그래밍 대신 이미지를 드래그 가능하게 한 뒤 모두 저장하면 '직접' 스크래핑을 할 수 있다. 하지만 이는 굉장히 반복적이고 지루하며 시간집약적인 작업이다. 이를 프로그래밍을 이용하면 자동화할 수 있을 뿐만 아니라 훨씬 더 빠르게 작업이 가능하다.
그렇다면 반복적인 '생각'을 자동화하기 위한 도구는 무엇일까? 나는 이것이 바로 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해준 코드를 보니 calculateClusterPosition
와 shuffleMatrixProcessing
에 해당하는 코드를 내가 전해주지 않았다는 것을 알 수 있었다. 이 해당하는 코드도 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를 통해 생각의 자동화
를 해 생산성을 높일 수 있으면 좋겠다.
이런 유용한 정보를 나눠주셔서 감사합니다.