開発していないエンジニアらしき人間がAI搭載エディタを使ってみた結果
はじめに(ポエム)
みなさん、こんにちは!開発してない開発エンジニアです!
最近の仕事って何かというと、チーム間の窓口だったり、本番障害の問い合わせ対応だったり、ちょっとしたコードレビューだったり、メンバーのタスク調整だったり...。気づいたらPMっぽいことばかりやらされてて、もう1年近く経っちゃいました(それ以前は開発してたんですよ!)
1年も経つとね、技術ってめちゃくちゃ進歩してるんですよ。ライブラリのバージョンはどんどん上がるし、LLMの世界からは完全に置いていかれてる状態。Cursor、Clineって何じゃらほいって感じです...
でも!忙しさが少し落ち着いて、新年度からチームが異動するって分かった去年の年末、ふと思ったんです。
「開発エンジニアを名乗ってる以上、技術には触れたいじゃん!」「そうだ、私の職種は開発エンジニアだよ!」「コード書かせろ~!」「技術自体は好きなんだよ!」って。
ZennやQiitaの記事は見てはいたんですけど、私の頭は文字だけじゃインプットできないタイプなんで、やっぱり手を動かそうと思って、今年の年始くらいから個人開発を再開し始めてました。
その中でも特に気になってたのが「AI搭載コードエディタってどれくらいすごいんだろう?」ということ。というのも、個人情報を扱う部署で働いてるから、業務で生成AIを使う機会がほとんどなくて。LLMに触れるのも、個人開発やドキュメント作成の時に壁打ちしてもらうくらいでした。
AI搭載エディタの力を借りると、TODOアプリとかどれだけ簡単に作れちゃうんだろう?って純粋に興味があったんです!
Clineを使ってTODOアプリ実装
まずはVSCodeの拡張機能にClineをいれてみました。
かわいいロボマークが出てきて、無料版を使おうと思ったので、GitHubアカウントと連携したらすぐに使えるようになりました!
さっそくClineの入力欄に雑に「honoを使ったtodoアプリを実装して」って入力してみました。トークン数とかの表示は出てますが、Clineが親切に指示をくれます。
initコマンドだけじゃなくて、コマンドの実行もボタンを押せばやってくれるみたい。すげー!
# ディレクトリの作成
mkdir hono-todo-app && cd hono-todo-app && npm init -y
# ライブラリのインストール
npm install hono @hono/node-server typescript ts-node --save && npm install @types/node --save-dev
- tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"skipLibCheck": true
},
"include": ["src/**/*"]
}
- index.ts
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { serveStatic } from '@hono/node-server/serve-static'
// Define Todo type
interface Todo {
id: string
text: string
completed: boolean
}
// Create Hono app
const app = new Hono()
// In-memory todos storage
let todos: Todo[] = []
// Serve static files from the public directory
app.use('/*', serveStatic({ root: './public' }))
// API routes
app.get('/api/todos', (c) => {
return c.json(todos)
})
app.post('/api/todos', async (c) => {
const { text } = await c.req.json<{ text: string }>()
if (!text) {
return c.json({ error: 'Text is required' }, 400)
}
const newTodo: Todo = {
id: Date.now().toString(),
text,
completed: false
}
todos.push(newTodo)
return c.json(newTodo, 201)
})
app.put('/api/todos/:id', async (c) => {
const id = c.req.param('id')
const { text, completed } = await c.req.json<{ text?: string, completed?: boolean }>()
const todoIndex = todos.findIndex(todo => todo.id === id)
if (todoIndex === -1) {
return c.json({ error: 'Todo not found' }, 404)
}
if (text !== undefined) {
todos[todoIndex].text = text
}
if (completed !== undefined) {
todos[todoIndex].completed = completed
}
return c.json(todos[todoIndex])
})
app.delete('/api/todos/:id', (c) => {
const id = c.req.param('id')
const todoIndex = todos.findIndex(todo => todo.id === id)
if (todoIndex === -1) {
return c.json({ error: 'Todo not found' }, 404)
}
todos = todos.filter(todo => todo.id !== id)
return c.json({ success: true })
})
// Start the server
const port = 3000
console.log(`Server is running on http://localhost:${port}`)
serve({
fetch: app.fetch,
port
})
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hono Todo App</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<h1>Hono Todo App</h1>
<div class="todo-input">
<input type="text" id="todo-text" placeholder="Add a new todo...">
<button id="add-todo">Add</button>
</div>
<div class="todo-filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="active">Active</button>
<button class="filter-btn" data-filter="completed">Completed</button>
</div>
<ul id="todo-list"></ul>
</div>
<script src="/script.js"></script>
</body>
</html>
- style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #2c3e50;
}
.todo-input {
display: flex;
margin-bottom: 20px;
}
.todo-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
.todo-input button {
padding: 10px 15px;
background-color: #3498db;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.todo-input button:hover {
background-color: #2980b9;
}
.todo-filters {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.filter-btn {
padding: 5px 10px;
margin: 0 5px;
background-color: #ecf0f1;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.filter-btn.active {
background-color: #3498db;
color: white;
border-color: #3498db;
}
#todo-list {
list-style: none;
}
.todo-item {
display: flex;
align-items: center;
padding: 10px;
background-color: white;
border-radius: 4px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.todo-item.completed {
opacity: 0.7;
}
.todo-checkbox {
margin-right: 10px;
cursor: pointer;
}
.todo-text {
flex: 1;
font-size: 16px;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #7f8c8d;
}
.todo-delete {
color: #e74c3c;
background: none;
border: none;
cursor: pointer;
font-size: 18px;
transition: color 0.3s;
}
.todo-delete:hover {
color: #c0392b;
}
- script.js
document.addEventListener('DOMContentLoaded', () => {
// DOM elements
const todoInput = document.getElementById('todo-text');
const addButton = document.getElementById('add-todo');
const todoList = document.getElementById('todo-list');
const filterButtons = document.querySelectorAll('.filter-btn');
// Current filter
let currentFilter = 'all';
// Fetch all todos from the API
const fetchTodos = async () => {
try {
const response = await fetch('/api/todos');
const todos = await response.json();
renderTodos(todos);
} catch (error) {
console.error('Error fetching todos:', error);
}
};
// Add a new todo
const addTodo = async () => {
const text = todoInput.value.trim();
if (!text) return;
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text })
});
if (response.ok) {
todoInput.value = '';
fetchTodos();
}
} catch (error) {
console.error('Error adding todo:', error);
}
};
// Toggle todo completed status
const toggleTodo = async (id, completed) => {
try {
await fetch(`/api/todos/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ completed })
});
fetchTodos();
} catch (error) {
console.error('Error updating todo:', error);
}
};
// Delete a todo
const deleteTodo = async (id) => {
try {
await fetch(`/api/todos/${id}`, {
method: 'DELETE'
});
fetchTodos();
} catch (error) {
console.error('Error deleting todo:', error);
}
};
// Render todos based on current filter
const renderTodos = (todos) => {
todoList.innerHTML = '';
const filteredTodos = todos.filter(todo => {
if (currentFilter === 'all') return true;
if (currentFilter === 'active') return !todo.completed;
if (currentFilter === 'completed') return todo.completed;
return true;
});
filteredTodos.forEach(todo => {
const li = document.createElement('li');
li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'todo-checkbox';
checkbox.checked = todo.completed;
checkbox.addEventListener('change', () => toggleTodo(todo.id, checkbox.checked));
const span = document.createElement('span');
span.className = 'todo-text';
span.textContent = todo.text;
const deleteButton = document.createElement('button');
deleteButton.className = 'todo-delete';
deleteButton.innerHTML = '×';
deleteButton.addEventListener('click', () => deleteTodo(todo.id));
li.appendChild(checkbox);
li.appendChild(span);
li.appendChild(deleteButton);
todoList.appendChild(li);
});
};
// Set up event listeners
addButton.addEventListener('click', addTodo);
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addTodo();
}
});
filterButtons.forEach(button => {
button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
currentFilter = button.dataset.filter;
fetchTodos();
});
});
// Initial fetch
fetchTodos();
});
- package.json
{
"name": "hono-todo-app",
"version": "1.0.0",
"description": "Todo application built with Hono",
"main": "dist/index.js",
"type": "module",
"scripts": {
"dev": "ts-node --esm src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"keywords": [
"hono",
"todo",
"typescript"
],
"author": "",
"license": "ISC",
"dependencies": {
"hono": "^3.0.0",
"@hono/node-server": "^1.0.0",
"typescript": "^5.0.0",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@types/node": "^18.0.0"
}
}
cd hono-todo-app && npm install
npm install
完成結果
なんと!言われるがままにファイルを作成し続けただけで、TODOアプリが実装できちゃいました!もちろん途中でClineの出す指示にお茶目なミスがあって少し修正はしましたけど、ほぼ何も考えずにローカル環境の起動までできてしまいました。コードの中身を理解せずに作るのはよくないんですけどね...でもファイル生成からコマンド実行まで、ボタンをポチポチするだけでできるなんて!
しかも、明確に「こういう機能がほしい」とまで入力してないのに、フィルター機能までつけてくれてるんですよ!(CRUDだけの雑なものができあがるかと思ってました)
おまけにブラウザ操作機能までついてるし。君、できる子だね!

まとめ
業務や個人開発でClaude 3やChatGPTと壁打ちしたり、コード改善のアドバイスをもらったりした経験はあったけど、ここまでAI搭載コードエディタが進化してるとは驚きました!「何を今さら」って思われてる方がほとんどだとは思いますし、私もこういうことができるのは知ってたんですが、いざ実際に体験すると、やっぱりすごいですね~。
今回の経験で感じたのは、エンジニアとしての焦りや不安だけじゃなくて、このAI搭載エディタを業務でどう活かせるか、ということですね。
月並みなことを言いますが、現実のソースコードはこんな簡単に設計・実装できるわけではないんですよね。仕様もソースも複雑に絡み合って動いてるケースがほとんどです。(でも、ちゃんとAIにコンテキストを渡してあげれば対応してくれそうな気もします。いわば「コンテキスト駆動開発」的な?笑)
AIの台頭で開発エンジニアのあり方が問われる昨今、こういったツールをどう使いこなしていくべきか考えさせられました。AIにすべてを任せたくはないし、AIの提示するコードの良し悪しを判断できる開発スキルはまだまだ必要だと思ってます!
だから、これからも日々技術を追いかけ続けていきたいと思います!!
以上、見てくださってありがとうございました~!
Discussion