ToCode
Відкрити в Telegram
1 419
Підписники
Немає даних24 години
Немає даних7 днів
-430 день
Архів дописів
1 419
sql\CASE WHEN ${t.type} = 'tool-getLocation' THEN ${t.tool_toolCallId} IS NOT NULL AND ${t.tool_state} IS NOT NULL ELSE TRUE END\,
),
check(
"data_weather_fields_required",
sql\CASE WHEN ${t.type} = 'data-weather' THEN ${t.data_weather_location} IS NOT NULL AND ${t.data_weather_weather} IS NOT NULL AND ${t.data_weather_temperature} IS NOT NULL ELSE TRUE END\,
),
],
);
export type MyDBUIMessagePart = typeof parts.$inferInsert;
export type MyDBUIMessagePartSelect = typeof parts.$inferSelect;
התוכן נשמר בטבלה שנקראת parts כאשר לכל הודעה יש הרבה חלקים. טבלה זו מכילה עמודות לכל "סוג" חלק ויותר מזה לכל כלי פוטנציאלי יש שתי עמודות עבור הקלט והפלט של אותו כלי. כששאלתי את קלוד על הסכימה הוא כתב בכתב מודגש "This is over-engineered for most AI conversation needs".
מכירים גישות נוספות? ניסיתם חלק מהגישות פה ורוצים לשתף איך היה? מוזמנים לשתף בתגובות בטלגרם או ברשתות החברתיות.1 419
"messages",
{
id: varchar()
.primaryKey()
.$defaultFn(() => generateId()),
chatId: varchar()
.references(() => chats.id, { onDelete: "cascade" })
.notNull(),
createdAt: timestamp().defaultNow().notNull(),
role: varchar().$type<MyUIMessage["role"]>().notNull(),
},
(table) => [
index("messages_chat_id_idx").on(table.chatId),
index("messages_chat_id_created_at_idx").on(table.chatId, table.createdAt),
],
);
export const parts = pgTable(
"parts",
{
id: varchar()
.primaryKey()
.$defaultFn(() => generateId()),
messageId: varchar()
.references(() => messages.id, { onDelete: "cascade" })
.notNull(),
type: varchar().$type<MyUIMessage["parts"][0]["type"]>().notNull(),
createdAt: timestamp().defaultNow().notNull(),
order: integer().notNull().default(0),
// Text fields
text_text: text(),
// Reasoning fields
reasoning_text: text(),
// File fields
file_mediaType: varchar(),
file_filename: varchar(), // optional
file_url: varchar(),
// Source url fields
source_url_sourceId: varchar(),
source_url_url: varchar(),
source_url_title: varchar(), // optional
// Source document fields
source_document_sourceId: varchar(),
source_document_mediaType: varchar(),
source_document_title: varchar(),
source_document_filename: varchar(), // optional
// shared tool call columns
tool_toolCallId: varchar(),
tool_state: varchar().$type<ToolUIPart["state"]>(),
tool_errorText: varchar().$type<ToolUIPart["state"]>(),
// tools inputs and outputss are stored in separate cols
tool_getWeatherInformation_input:
jsonb().$type<getWeatherInformationInput>(),
tool_getWeatherInformation_output:
jsonb().$type<getWeatherInformationOutput>(),
tool_getLocation_input: jsonb().$type<getLocationInput>(),
tool_getLocation_output: jsonb().$type<getLocationOutput>(),
// Data parts
data_weather_id: varchar().$defaultFn(() => generateId()),
data_weather_location: varchar().$type<MyDataPart["weather"]["location"]>(),
data_weather_weather: varchar().$type<MyDataPart["weather"]["weather"]>(),
data_weather_temperature:
real().$type<MyDataPart["weather"]["temperature"]>(),
providerMetadata: jsonb().$type<MyProviderMetadata>(),
},
(t) => [
// Indexes for performance optimisation
index("parts_message_id_idx").on(t.messageId),
index("parts_message_id_order_idx").on(t.messageId, t.order),
// Check constraints
check(
"text_text_required_if_type_is_text",
// This SQL expression enforces: if type = 'text' then text_text IS NOT NULL
sql\CASE WHEN ${t.type} = 'text' THEN ${t.text_text} IS NOT NULL ELSE TRUE END\,
),
check(
"reasoning_text_required_if_type_is_reasoning",
sql\CASE WHEN ${t.type} = 'reasoning' THEN ${t.reasoning_text} IS NOT NULL ELSE TRUE END\,
),
check(
"file_fields_required_if_type_is_file",
sql\CASE WHEN ${t.type} = 'file' THEN ${t.file_mediaType} IS NOT NULL AND ${t.file_url} IS NOT NULL ELSE TRUE END\,
),
check(
"source_url_fields_required_if_type_is_source_url",
sql\CASE WHEN ${t.type} = 'source_url' THEN ${t.source_url_sourceId} IS NOT NULL AND ${t.source_url_url} IS NOT NULL ELSE TRUE END\,
),
check(
"source_document_fields_required_if_type_is_source_document",
sql\CASE WHEN ${t.type} = 'source_document' THEN ${t.source_document_sourceId} IS NOT NULL AND ${t.source_document_mediaType} IS NOT NULL AND ${t.source_document_title} IS NOT NULL ELSE TRUE END\,
),
check(
"tool_getWeatherInformation_fields_required",
sql\CASE WHEN ${t.type} = 'tool-getWeatherInformation' THEN ${t.tool_toolCallId} IS NOT NULL AND ${t.tool_state} IS NOT NULL ELSE TRUE END\,
),
check(
"tool_getLocation_fields_required",1 419
פריימוורקים אג'נטיים ושמירת הודעות בבסיס הנתונים
אחד הפערים בין ספריות לכתיבת מערכות משולבות AI הוא האופן בו מערכות אלה שומרות הודעות בבסיס הנתונים. בואו נראה שלוש תבניות מרכזיות ומהן נלמד על מה חשוב להסתכל.
נתחיל עם OpenAI Agents SDK, ספריה יחסית חדשה של OpenAI. זאת הדוגמה מתוך התיעוד שלהם:
import asyncio
from agents import Agent, Runner, SQLiteSession
async def main():
agent = Agent(name="Assistant", instructions="Reply very concisely.")
# Create session instance
session = SQLiteSession("conversation_123", "chats.db")
# First turn
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?", session=session)
print(result.final_output)
# San Francisco
# Second turn - agent automatically remembers previous context
result = await Runner.run(agent, "What state is it in?", session=session)
print(result.final_output)
# California
if __name__ == '__main__':
asyncio.run(main())
הספריה שומרת את כל ההודעות הישנות בבסיס נתונים SQLite או בזיכרון. בדף התיעוד הם מציעים לי לכתוב Custom Session Backend אם אני רוצה להשתמש בספריה בפרודקשן כדי לשמור את השיחות ב Postgresql או רדיס, אבל הם לא מספקים אחד. בכניסה לבסיס הנתונים שנוצר אני יכול לזהות שם טבלה של שיחות וטבלה של הודעות:
sqlite> .tables
agent_messages agent_sessions
sqlite> select * from agent_messages ;
1|conversation_123|{"content": "What city is the Golden Gate Bridge in?", "role": "user"}|2025-07-30 13:35:36
2|conversation_123|{"id": "msg_688a1fa7fbe0819b9f4096e23741045f0d855ca99eba94a7", "content": [{"annotations": [], "text": "San Francisco.", "type": "output_text", "logprobs": []}], "role": "assistant", "status": "completed", "type": "message"}|2025-07-30 13:35:36
3|conversation_123|{"content": "What state is it in?", "role": "user"}|2025-07-30 13:35:37
4|conversation_123|{"id": "msg_688a1fa8fde0819b958bac6b45c84f900d855ca99eba94a7", "content": [{"annotations": [], "text": "California.", "type": "output_text", "logprobs": []}], "role": "assistant", "status": "completed", "type": "message"}|2025-07-30 13:35:37
sqlite> select * from agent_sessions ;
conversation_123|2025-07-30 13:35:36|2025-07-30 13:35:37
כל הודעה נשמרת בתור JSON בלי סכימה מסודרת. יש פה יתרון שהמימוש פשוט ואם יהיו לנו חלקים נוספים להודעה יהיה קל להוסיף הודעות חדשות עם אותם חלקים, אבל גם חיסרון שקשה להריץ שאילתות על JSON או לוודא נכונות של התוכן.
ספריה שניה היא RubyLLM בשפת רובי שמתחברת עם ריילס ומציעה את המבנה הבא:
rails g model Chat model_id:string user:references
rails g model Message chat:references role:string content:text model_id:string input_tokens:integer output_tokens:integer tool_call:references
rails g model ToolCall message:references tool_call_id:string:index name:string arguments:jsonb
השינוי הראשון שאנחנו רואים הוא הבחירה להשתמש בסכימה מסודרת, לכל הודעה יש תוכן, role, טוקנים ואיזה מודל יצר אותה, ובנוסף יש לנו טבלה של הפעלת כלים ששומרת את הפרמטרים ושמות כל הכלים שנקראו. כל ToolCall מחובר לשתי הודעות, ההודעה שהפעילה את הכלי וההודעה עם התוצאה. יש פה צעד קדימה מבחינת בטיחות המידע וחיבור לבסיסי נתונים אמיתיים. חלקים אחרים בהודעה כמו קבצים או תמונות יישמרו בטבלה נפרדת של קבצים מצורפים במנגנון Active Storage. משתמשים שרוצים יכולים להוסיף עוד עמודות לטבלת ההודעות כמו metadata.
ספריה שלישית לסקירה היא Vercel AI SDK והיא מציעה את הפיתרון המורכב מכולן. כך נראית סכימה מהדוגמה שלהם:
import {
check,
index,
integer,
jsonb,
pgTable,
real,
text,
timestamp,
varchar,
} from "drizzle-orm/pg-core";
import { MyDataPart, MyUIMessage, MyProviderMetadata } from "../message-type";
import { generateId, ToolUIPart } from "ai";
import { sql } from "drizzle-orm";
import {
getLocationInput,
getLocationOutput,
getWeatherInformationInput,
getWeatherInformationOutput,
} from "@/ai/tools";
export const chats = pgTable("chats", {
id: varchar()
.primaryKey()
.$defaultFn(() => generateId()),
});
export const messages = pgTable(1 419
הזמנה לוובינר: בואו נכתוב שרת MCP
בעבודה עם AI כח על שמאוד עוזר ל AI ליצור קוד טוב יותר הוא היכולת להפעיל "כלים" - כלומר לבקש מסביבת הפיתוח לעשות משהו, לקבל את התוצאה ולהמשיך ליצור טקסט לפי התוצאה שהתקבלה. סביבות פיתוח משולבות AI יודעות להתחבר לכלים חיצוניים דרך ארכיטקטורה שנקראת MCP, קיצור של Model Context Protocol.
אם אתם עובדים בסביבת פיתוח משולבת AI והתנסיתם במצב סוכן של הסביבה כבר ראיתם עבודה עם כלים - זה הסוכן שמבקש לקבל תוכן של קובץ, להריץ פקודה בשורת הפקודה או אפילו להתחבר לבסיס הנתונים, לגיטהאב או לאינטרנט. חלק מהכלים הגיעו מובנים בסביבה, את חלקם אולי התקנתם בעצמכם דרך האינטרנט. והאמת שמאוד קל גם לכתוב כלים כאלה בעצמכם.
מחר (ה 31.7) בעשר בבוקר אראה לכם בשיחת זום פתוחה איך לכתוב שרת MCP ולהעלות אותו לאינטרנט לתשתית של Cloudflare Worker. אנחנו נראה:
1. איך לפתוח פרויקט MCP Server חדש בטייפסקריפט.
2. נכיר את המבנה של שרת MCP שיודע לעבוד על קלאודפלייר דרך ספריית הסוכנים שלהם.
3. נחבר את שרת ה MCP לאחסון KV בקלאודפלייר ונראה מה ההבדל בין סטייט של השרת לבין מידע קבוע שנשמר.
4. נדבר על אותנטיקציה ונבין איך עובד ניהול גישה לשרת עם OAuth.
כרגיל במפגשים אלה תהיה שיחה פתוחה עם המון מקום לשאלות והארות שלכם. לא תהיה הקלטה אבל אם אתם לא מספיקים להגיע תכתבו לי ונארגן שידור חוזר.
אם אתם בקבוצה אז יש לכם כבר את הלינק לזום מהמפגשים הקודמים, ואם עדיין לא נכנסתם אז צריך רק להזין את המייל בתיבה בעמוד הזה:
https://tocode.ravpage.co.il/tocodeai
ואתם מקבלים את הלינק לזום ישר למייל כמו גם הזמנות למפגשים הבאים בנושאי AI.
נתראה בזום.
1 419
פערי שפה
כשמנהל מוצר או מנהל בדיקות כותב במסמך בדיקה "נכנסים לתיבת דואר נכנס ומוודאים שאפשר לראות את כל ההודעות" זה אולי מתאר מה שבן אדם עושה אבל זה לא עוזר לכתוב בדיקה אוטומטית.
וכשבודק אוטומציה כותב במסמך בדיקה את השלבים המפורטים יותר של הבדיקה זה עוזר לממש אבל הרבה יותר קשה לקרוא מהר רצף של בדיקות ולהבין מה בדקנו ומה לא.
לפני שניגשים לכתוב מסמך טכני כדאי לוודא שאנחנו מבינים - מה אנחנו כותבים, למי אנחנו כותבים ולאיזו מטרה. וכן AI יכול מאוד לעזור לעבור בין השפות אבל כל מעבר כזה דורש הרבה השלמות של קונטקסט וכדאי להיות ערניים לאי-דיוקים.
1 419
אני מתכנתת פרונט-אנד ופוחדת ש AI ייקח לי את העבודה
מתכנתת פרונט אנד כתבה ברדיט-
> התחלתי להשתמש בכלי AI לקידוד וזה מטורף. אני לא יודעת איפה להתחיל. אני בטוחה שבקרוב העבודה שלי תהיה מיותרת.
סיכום התגובות של האנשים לא צריך להפתיע אף אחד מאיתנו וחשוב להשאיר אותו בראש ולהישאר מפוקסים בעבודה עם כלים אלה:
1. השקיעי בלימוד ה Basics: תלמדי ארכיטקטורה, תלמדי איך החלקים השונים של המערכת עובדים יחד, תלמדי מה זה קוד נקי ואיך נראה קוד שקשה לתחזק. למדי איך לכתוב ולערוך קוד בצורה שניתנת לתחזוקה לאורך זמן.
2. ל AI יש נטייה לכתוב פיתרונות שעובדים באמצעות שכפול תבניות מוכרות. רוב הזמן הפיתרונות האלה בזבזניים או לא מתאימים ל Use Case. למדי לזהות פיתרונות טובים ולא טובים ולדרוש את הפיתרונות הנכונים. שימי לב ש AI כותב המון useEffect גם כשלא צריך ולא יודע לחלק פיתרון לקומפוננטות. התמקדי במיומנויות אלה.
3. לא, AI הוא לא "טוב ברמות אחרות" אלא הוא מאתגר את הבינוניות. מי שרק רוצה לבוא לעבודה כדי להדביק חלקי קוד מהאינטרנט יצטרך למצוא עבודה אחרת.
יהיו שינויים. יהיו משרות שייסגרו ואחרות שייפתחו. וזה יקרה מהר. ככל ש AI יכתוב יותר קוד כך נצטרך יותר מפתחים ברמה גבוהה שיוכלו לערוך את הקוד שנוצר, לסדר אותו במקום וליצור מבנים טובים יותר.
1 419
כמה רעיונות יותר טובים מהשלמת טאבים כדי ללמוד
יוצרי קרסר סיפרו בראיון שהיה להם חשוב להשקיע בהשלמת קוד אוטומטית עם טאב ולהרחיב את הפיצ'ר מעבר לרק השלמת קוד. בקרסר הטאב גם קופץ למקום הבא שתרצו לערוך, משנה קוד ישן או מוחק קוד. הטיעון שלהם היה שתכנות מערב מעט נקודות החלטה (חשיבה) והרבה המשך אוטומטי לאותה החלטה.
ויש בזה לא מעט.
היום כשאני כותב קוד עם LLM אני רואה את התופעה הזו בבירור: ה LLM מצליח לבנות פיצ'רים מהר כשהתשתית בנויה לפיצ'ר החדש, אבל הוא לא יכול לשים לב ולהחליט החלטות יצירתיות לגבי הקוד, החלטות שיאפשרו לו לבנות את הסט הבא של הפיצ'רים.
בחזרה לטאב - אז השלמת הקוד עם טאב משחקת משחק כזה, ממילא יש לך מעט נקודות חשיבה והרבה כתיבה אוטומטית של קוד, אנחנו לא יודעים בצורה אוטומטית מתי צריך לחשוב ומתי צריך לרוץ אוטומטית אז נרוץ תמיד אוטומטית ואם צריך לחשוב אתה אחראי ללחוץ על הבלמים (במקרה של טאב זה כפתור Escape), לחשוב ולכתוב קוד אחר. מפתחים רבים מרגישים שמנגנון זה עוזר להם ללמוד: הם עשו את החשיבה אבל עכשיו לא בטוחים איך יראה הסינטקס וצפייה בקוד שנכתב אוטומטית יכולה לעזור לקבל רעיונות למימושים. הבעיה שבני אדם לא טובים במיוחד בלימוד דרך צפייה באחרים. גם אם יש הארות מעניינות שנרוויח מצפיה בקוד שנכתב, הרוב יישכח מהר.
במקום לצפות בקרסר או קופיילוט כותב קוד הנה כמה דברים שתוכלו לנסות ויותר יעזרו לכם להתקדם בפיתוח:
1. לדבר עם AI בחלון אחד, לכתוב לבד בחלון אחר - כי אין תחליף להקלדה. בונוס, אחרי שהפיצ'ר עובד שומרים את הקוד בענף אחר בגיט, חוזרים לענף המקורי ומקלידים מאפס את הפיתרון.
2. לתת ל AI לכתוב את הקוד דרך Agent Mode בחלון הצד ואז לעבור על הקוד ולשאול עליו שאלות. דיאלוג מאלץ אותנו להבין ולחשוב יותר מאשר צפייה במישהו אחר עושה.
3. לתת ל AI לכתוב את הקוד ולהדביק את התוצאה לחלון שיחה נפרד בדפדפן, ושם לבקש הסבר על הקוד שנכתב ותרגילים שיעזרו לכם להבין אותו טוב יותר.
4. לכתוב תיעוד לפני שה AI מקודד ולתת ל AI לממש. שחקו עם גירסאות שונות של התיעוד ותראו מימושים שונים של AI.
5. להסתכל על מימוש ספציפי ולכתוב לו תיעוד, אחרי זה להדביק את התיעוד שכתבתם והמימוש לחלון שיחה נפרד ולבקש פידבק.
6. לתת ל AI לממש ולהעתיק אלינו לקוד כל פעם פונקציה או בלוק קצר אפילו ב Copy/Paste. לקרוא, לוודא שהבנתם ולתת Code Review ל AI על כל בלוק כזה.
כן אני מבין שכל אחת מההצעות שלי כאן לוקחת יותר זמן מלהתבונן ב AI כותב קוד תוך כדי לחיצות על Tab. וזה בסדר - גם אם ה AI עוזר לכתוב קוד מהר יותר, אין לו איך לעזור לנו להבין מהר יותר.
1 419
טיפ רובי: מילים ואינדקסים במחרוזת
לרובי אין בעיה להפריד מחרוזת למילים עם אותה פקודת split שאנחנו מכירים גם משפות אחרות:
3.3.5 :027 > "hello world".split
=> ["hello", "world"]
אבל כשיוצאים מעולם הדוגמאות הקטנות הקוד הזה מתחיל לאכזב. הנה עוד ניסיון עם משפט יותר מאתגר:
3.3.5 :029 > "Don't. go. there".split
=> ["Don't.", "go.", "there"]
שהיה עובד הרבה יותר טוב בלי הנקודות שצמודות למילים. דרך אחת לסדר את זה היא לחפש "התאמות" במקום "הפרדות" ובשביל זה יש לנו את scan:
3.3.5 :032 > "Don't. go. there".scan(/\p{L}+(?:'\p{L}+)*/u)
=> ["Don't", "go", "there"]
ל scan אני נותן ביטוי רגולארי, במקרה שלנו ביטוי רגולארי שמחפש אותיות, אחרי זה אולי גרש (בגלל מילים כמו Don't) ואז עוד אותיות. זה עוזר כדי לקבל רק את המילה, אבל עכשיו מה אם אנחנו רוצים גם את האינדקס של כל מופע? אי אפשר פשוט לחפש את האינדקס אחרי שכבר מצאנו את ההתאמה כי לפעמים יש מילים כפולות:
3.3.5 :036 > text = "Don't means Don't - Don't press the button"
=> "Don't means Don't - Don't press the button"
3.3.5 :037 > third_word = text.scan(/\p{L}+(?:'\p{L}+)*/u)[2]
=> "Don't"
3.3.5 :038 > index_of_third_word = text.index(third_word)
=> 0
זה החזיר 0 כי המילה השלישית זהה למילה הראשונה.
דרך אחת שעבדה לי כדי לפתור את זה ברובי היא ליצור Enumerator של ההתאמות לביטוי הרגולארי. מכל התאמה נוכל לקבל את הטקסט וגם את אינדקס ההתחלה שלה, ובגלל שאנחנו ברובי אפשר להוסיף את זה למחלקה String כדי שאפשר יהיה לקרוא לקוד על כל מחרוזת. הנה הקוד:
3.3.5 :039 > class String
3.3.5 :040 > def tokenize
3.3.5 :041 > regex = Regexp.new(/\p{L}+(?:'\p{L}+)*/u)
3.3.5 :042 >
3.3.5 :043 > Enumerator.new do |y|
3.3.5 :044 > pos = 0
3.3.5 :045 > while m = regex.match(self, pos)
3.3.5 :046 > y << m
3.3.5 :047 > pos = m.end(0)
3.3.5 :048 > end
3.3.5 :049 > end
3.3.5 :050 > end
3.3.5 :051 > end
3.3.5 :056 > text.tokenize.to_a
=>
[#<MatchData "Don't">,
#<MatchData "means">,
#<MatchData "Don't">,
#<MatchData "Don't">,
#<MatchData "press">,
#<MatchData "the">,
#<MatchData "button">]
עכשיו בשביל לקבל את כל המילים ואינדקס ההתחלה של כל מילה אני כותב:
3.3.5 :057 > text.tokenize.map { |m| [m.to_s, m.begin(0)] }
=> [["Don't", 0], ["means", 6], ["Don't", 12], ["Don't", 20], ["press", 26], ["the", 32], ["button", 36]]
ובשביל לקבל את אינדקס ההתחלה של המילה השלישית אני כותב:
3.3.5 :060 > text.tokenize.drop(2).first.begin(0)
=> 121 419
ואם קלוד החזיר לי PR של 700 שורות?
יש תיקוני באגים שדורשים שבועות של מחקר עד שאנחנו מוצאים את הפסיק הזה שצריך להוסיף ופותר את כל הבעיה.
ויש באגים שייקח שבועות לתקן כי התיקון דורש חשיבה מחדש על המבנה הבסיסי של המערכת.
יש פיצ'רים שמאוד קל לכתוב ולבדוק, ואחרים שנצטרך לעבוד קשה בשביל להבין אם הם עובדים בכל המצבים.
יש מערכות שמכילות המון מידע של לקוחות וכל שינוי ב DB צריך לעשות בפינצטה, ויש סטארטאפים שעדיין אין להם לקוחות ואין שום בעיה לשבור את כל ה DB ולבנות מחדש.
תיקון בעיית ביצועים זה לא מחקר על הטמעת טכנולוגיה חדשה וזה לא התאמת אתר לתקן נגישות.
פיתוח תוכנה זה עבודה שמורכבת מהמון סוגים של משימות ברמות קושי שונות. יכול להיות שבעתיד יהיה AI שיוכל לפתור את כל הבעיות האלה אבל בינתיים אנחנו לא שם. כשמישהו אומר שהוא נותן לקלוד לכתוב 100% מקוד המערכת או מתאר Flow מסוים לעבודה עם AI ש"תמיד עובד" אני לוקח צעד אחורה.
כש AI מסוגל לכתוב את הקוד שאני רוצה אני שמח לשלב את הקוד בפרויקט. כש AI לא מצליח לכתוב את הקוד שאני רוצה כי אפילו אני עוד לא יודע מה אני רוצה אני שמח לראות כמה גירסאות ובסוף לבחור מהן או לכתוב משהו אחר בעצמי. כש AI עוזר לי למצוא באג אני שמח להטמיע את התיקון שלו, וכש AI לא מצליח לפתור את הבאג אני לא לוקח ללב ומתקן בעצמי. וכש AI מתעקש לכתוב פיתרון של 700 שורות שאני לא יכול להבין לבעיה אני אומר לא תודה והולך לדייק את הבעיה.
המטרה היא לא לתת לקלוד לכתוב 100% מהקוד. המטרה היא להשתמש בקלוד כדי להתקדם מהר יותר ולבנות מערכות יציבות יותר, יפות יותר וטובות יותר.
1 419
לחפש את ה"נכון"
אחד היתרונות של מפתחים מנוסים שמתחילים לעבוד עם AI הוא היכולת להבדיל בין קודבייס "נכון" לקודבייס מסורבל. מרסל ניומן סיפק דוגמה לכזאת אבחנה בפוסט שפרסם על פיתוח אפליקציית Cross Platform עם React Native.
מרסל הגיע לפרויקט בלי להכיר React Native אבל עם ניסיון של כעשר שנים בפיתוח תוכנה. הוא התחיל עם Vibe Coding, נתן ל AI לבנות הכל, ואז נכנס להסתכל על הקוד, הבין שיש פה בעיה וכמו שהוא מתאר את זה "יחד עם ה AI ביצענו ריפקטור לכל פיצ'ר ביישום עד שהקוד יהיה נקי". התהליך עצמו היה מבחינתו כמו צפייה בסרט - הוא שואל את ה AI שאלות וה AI מלמד אותו ריאקט ו React Native, תוך כדי שהוא מסביר כל פיצ'ר במערכת ודרך השאלות של מרסל גם מבצע את הריפקטורינג לגירסה יותר פשוטה.
היכולת לראות עבודה ולהבין מהר מאוד מה האיכות שלה ואיפה אפשר לשפר תמיד היתה חשובה, אבל עכשיו היא משמעותית יותר מתגמלת. היא מאפשרת לא רק לקבל או לדחות קוד AI, אלא גם לדרוש מה AI שיפורים ספציפיים וכך הקוד כולו הופך טוב יותר.
האם יום אחד נוכל להשתמש ב AI כדי להבין אם AI אחר לא בכיוון ולנווט אותו לכתוב קוד טוב יותר? אני לא יודע. בינתיים זה עוד משהו ששווה לנו להתמקצע בו. ככל שה Feedback Loop של כתיבת קוד מתקצר כך אנחנו יכולים לנסות עוד פרומפטים ועוד Refactor-ים עד שנגיע לאותו קוד שכיף לעבוד עליו.
(נ.ב. אותו רעיון נכון גם מחוץ לעולם הקוד - אנשים שמנוסים בחיפוש עבודה יודעים מתי חיפוש עבודה "לא מרגיש נכון" וצריך לשנות משהו; אנשים שמנוסים בפתיחת עסקים יודעים מתי עסק "לא מרגיש נכון" וצריך לשנות כיוון; אנשים שמנוסים בשיווק ידעו להגיד מתי קמפיין שיווקי לא עובד וצריך לשנות. בכל המקרים אנחנו מדברים על תהליכים שדורשים התמדה בכיוון הנכון. כשצריך לשנות כיוון אנחנו מרוויחים ככל שמשנים כיוון יותר מוקדם, וכשצריך להתמיד אנחנו מרוויחים ככל שנתמיד עד להשגת התוצאה).
1 419
תשומת לב - המיומנות של העשור הבא
חברה נתנה ל AI לכתוב פיצ'ר שמוחק רווחים מהתחלה או סוף של שם לפני שמירה. ה AI כתב את הקוד וגם כתב לו את הבדיקה הבאה:
test 'Person name with only spaces is trimmed to empty' do
b = Person.new(name: ' ')
b.save
assert_equal('', b.name)
end
הבדיקה עברה ואני קיבלתי את ה PR. הבעיה היחידה? במערכת יש וולידציה שמוודאת ש name לא יכול להיות ריק.
בקריאה שניה ברור מה קורה פה - פקודת save של ריילס מחזירה ערך בוליאני אם השמירה הצליחה או נכשלה. אף אחד לא מסתכל על ערך זה ובעצם ה AI הפעיל save רק בשביל להטריג את הקוד שלו שמוחק רווחים ואז הוא מוודא שהרווח נמחק. אבל עכשיו השארנו בקוד מוקש. אם בעתיד AI או בן אדם ייתקל בבדיקה הזאת הם עלולים לחשוב שיכולים להיות אוביקטי Person עם שמות ריקים.
יש לא מעט מפתחי AI שיתנו ל AI לסדר את זה. אני מודה שאני לא אחד מהם ובמקרים כאלה אני נכנס לקוד ומתקן ידנית את השורה האמצעית:
test 'Person name with only spaces is trimmed to empty' do
b = Person.new(name: ' ')
b.valid? # trigger validation to trim b.name
assert_equal('', b.name)
end
בכל מקרה בעיניי זה היופי בפיתוח בעזרת AI - כשדברים עובדים אני יכול להתמקד באיך לשמור על הקוד נקי כי את עבודת ההקלדה עושה ה AI.
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
