ToCode
Відкрити в Telegram
1 418
Підписники
-224 години
-37 днів
-630 день
Архів дописів
1 418
force_path_style: true, # Enable 'force_path_style' => true, if bucket name is non DNS compliant
)
bucket_name = "test-bucket"
wrapper = BucketCreateWrapper.new(Aws::S3::Bucket.new(bucket_name))
return unless wrapper.create?(region)
puts "Created bucket #{wrapper.bucket.name}."
puts "Your bucket's region is: #{wrapper.location}"
end
run_demo if $PROGRAM_NAME == __FILE__
סקריפט שני כותב קובץ לאותו bucket:
require 'aws-sdk-s3'
region = "us-east-2"
Aws.config.update(
endpoint: 'http://s3.localhost.localstack.cloud:4566', # update with localstack endpoint
region: region,
force_path_style: true, # Enable 'force_path_style' => true, if bucket name is non DNS compliant
)
s3_client = Aws::S3::Client.new
bucket_name = 'test-bucket'
object_key = 'demo.txt'
file_content = "This is a demo file uploaded via Ruby to my LocalStack S3.\n"
begin
response = s3_client.put_object(
bucket: bucket_name,
key: object_key,
body: file_content
)
puts "File '#{object_key}' uploaded successfully to '#{bucket_name}'!"
rescue Aws::S3::Errors::ServiceError => e
puts "Error uploading file: #{e.message}"
end
וסקריפט שלישי עדיין ברובי קורא את הקובץ ומדפיס את תוכנו:
require 'aws-sdk-s3'
Aws.config.update(
endpoint: 'http://s3.localhost.localstack.cloud:4566', # update with localstack endpoint
region: 'us-east-2',
force_path_style: true # Enable 'force_path_style' => true, if bucket name is non DNS complian
)
s3_client = Aws::S3::Client.new
bucket_name = 'test-bucket'
object_key = 'demo.txt'
begin
response = s3_client.get_object(bucket: bucket_name, key: object_key)
file_contents = response.body.read
puts "Contents of '#{object_key}' in bucket '#{bucket_name}':\n\n"
puts file_contents
rescue Aws::S3::Errors::NoSuchKey => e
puts "File '#{object_key}' not found: #{e.message}"
rescue Aws::S3::Errors::ServiceError => e
puts "Error retrieving file: #{e.message}"
end
לסיום אפשר למחוק את הבאקט עם הסקריפט פייתון שהתקנו קודם בהפעלת:
$ awslocal s3 rb s3://test-bucket --force
סך הכל הפיתוח עם local stack מאפשר עבודה באותו ממשק כמו AWS אבל מקומית לגמרי. ברור שבסיום הפיתוח צריך לבדוק הכל על AWS ובטח יהיו בעיות ועוד צריך לדבר על הרשאות והכל נכון. אבל למי שרוצה להתחיל פיתוח שיגיע ל AWS אבל בינתיים רק לראות שדברים עובדים אצלנו על המחשב לוקאל סטאק יכול לתת פיתרון מצוין ומהיר.1 418
צעדים ראשונים עם localstack
לוקאל סטאק https://github.com/localstack/localstack הוא פרויקט שמטרתו הפעלת גירסה מקומית של AWS אצלכם על המחשב בתוך קונטיינר. הם מכסים עשרות סרביסים של AWS בחשבון החינמי, ויש גם חשבון בתשלום שכולל יותר יכולות לחלק מהסרביסים ויותר סרביסים. מה שאהבתי במיוחד בעבודה עם local stack זה שהקוד לא צריך להשתנות - הממשק שלהם זהה לממשק של AWS מלבד פקודת קונפיגורציה אחת שמגדירה את ה endpoint שלהם במקום של AWS.
בואו נראה איך להתקין ולעבוד עם סרביס S3 של לוקאל סטאק דרך שלושה סקריפטים.
התקנת הכלים
את לוקאל סטאק אפשר להתקין מדף ההתקנה שלהם כאן:
https://docs.localstack.cloud/getting-started/installation/
אחרי ההתקנה הייתי צריך להוסיף את התיקיה שלהם לתיקיות הקבצים של Docker Desktop מתוך ההגדרות של דוקר, וגם להתקין סקריפט שעוטף את aws באמצעות הפקודה:
pip install awscli-local
הפעלתי את local stack עם:
$ localstack start -d
ואחרי זה הרצתי את פקודת הסטטוס כדי לראות את הסרביסים שקיבלתי:
$ localstack status services
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Service ┃ Status ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ acm │ ✔ available │
│ apigateway │ ✔ available │
│ cloudformation │ ✔ available │
│ cloudwatch │ ✔ available │
│ config │ ✔ available │
│ dynamodb │ ✔ available │
│ dynamodbstreams │ ✔ available │
│ ec2 │ ✔ available │
│ es │ ✔ available │
│ events │ ✔ available │
│ firehose │ ✔ available │
│ iam │ ✔ available │
│ kinesis │ ✔ available │
│ kms │ ✔ available │
│ lambda │ ✔ available │
│ logs │ ✔ available │
│ opensearch │ ✔ available │
│ redshift │ ✔ available │
│ resource-groups │ ✔ available │
│ resourcegroupstaggingapi │ ✔ available │
│ route53 │ ✔ available │
│ route53resolver │ ✔ available │
│ s3 │ ✔ running │
│ s3control │ ✔ available │
│ scheduler │ ✔ available │
│ secretsmanager │ ✔ available │
│ ses │ ✔ available │
│ sns │ ✔ available │
│ sqs │ ✔ available │
│ ssm │ ✔ available │
│ stepfunctions │ ✔ available │
│ sts │ ✔ available │
│ support │ ✔ available │
│ swf │ ✔ available │
│ transcribe │ ✔ available │
└──────────────────────────┴─────────────┘
עבודה עם S3
נמשיך לכמה סקריפטים ב Ruby שעובדים עם S3 בשביל הדוגמה. בכולם השורה הכי חשובה היא:
Aws.config.update(
endpoint: 'http://s3.localhost.localstack.cloud:4566'
region: 'us-east-2',
force_path_style: true
)
הסקריפט הראשון יוצר bucket חדש על s3 שרץ על local stack:
require "aws-sdk-s3"
class BucketCreateWrapper
attr_reader :bucket
def initialize(bucket)
@bucket = bucket
end
def create?(region)
@bucket.create(create_bucket_configuration: { location_constraint: region })
true
rescue Aws::Errors::ServiceError => e
puts "Couldn't create bucket. Here's why: #{e.message}"
false
end
def location
if @bucket.nil?
"None. You must create a bucket before you can get its location!"
else
@bucket.client.get_bucket_location(bucket: @bucket.name).location_constraint
end
rescue Aws::Errors::ServiceError => e
"Couldn't get the location of #{@bucket.name}. Here's why: #{e.message}"
end
end
def run_demo
region = "us-east-2"
Aws.config.update(
endpoint: 'http://s3.localhost.localstack.cloud:4566', # update with localstack endpoint
region: region,1 418
קצת קשה להתמצא באתר שלהם, אבל בתחתית העמוד יש מספר לינקים שימושיים ובמיוחד שווה לחטט בדף Trending Vals. תוכלו למצוא שם אינסוף סקריפטים שאנשים אחרים כתבו ותוכלו לשלב אותם בעבודה שלכם. בצד ימין למעלה יש אייקון של זכוכית מגדלת שיפתח תיבת חיפוש אם אתם רוצים לחפש סקריפטים לפי טקסט.
בנוסף הסקריפטים מקבלים גישה למספר משאבים על val.town שיעזרו לכם לכתוב דברים שימושיים: הם יודעים לשלוח אימייל, לשמור מידע בבסיס נתונים, לשמור קבצים מקומית וכמו שכבר סיפרתי לתקשר עם ChatGPT.
וכאילו שזה לא מספיק יש להם סוכן AI שכותב את הסקריפטים ומריץ אותם כך שלכם נשאר רק להביא את הרעיון.
נ.ב. העליתי לטלגרם את הבוט תרגום שתיארתי כאן. קוראים
@SpeakEnglishToMe_bot ואתם מוזמנים לשלוח אליו מילים הוא יענה בתרגום. או מה שיותר מעניין, קחו את התבנית מכאן, תעשו fork ב val town ותעלו בוטים משלכם.1 418
ואל טאון הוא הפרויקט הכי מדליק שאתם לא מכירים
אתמול פרסמתי פה סקריפט שכתבתי שמחפש מידע על פודקסטים ב val.town. כן הכרתי את val.town בעיקר בזכות הסלוגן עם החרוז שלהם וקצת זכרתי שזה אתר שיתוף קוד כמו קודפן רק לצד שרת, אבל המשחקים איתו בימים האחרונים השאירו אותי בפה פעור. ואל טאון הוא הפרויקט הכי מדליק שאתם לא מכירים, ואנסה להסביר למה:
סקריפטים פשוטים ב val town
ואל טאון מאפשר לכם לכתוב סקריפטים כולל מאוד קצרים (סקריפט של פונקציה בודדת הולך טוב). את הסקריפטים האלה אפשר להריץ ידנית או מתוך סקריפטים אחרים, מה שנותן לנו מגרש משחקים לבניית מגדלים מלגו. הנה סקריפט פשוט שכתבתי שמתרגם טקסט דרך ChatGPT מכל שפה לאנגלית:
import { OpenAI } from "https://esm.town/v/std/openai";
export default async function translateToEnglishWithOpenAI(text: string) {
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [
{
role: "user",
content:
\Translate "שלום" to English. If the word the was already in English respond with its definition. Respond just with the translation or definition.\,
},
{ role: "assistant", content: \hello\ },
{
role: "user",
content:
\Translate "peace" to English. If the word the was already in English respond with its definition. Respond just with the translation or definition.\,
},
{
role: "assistant",
content:
\Peace is a stress-free state of security and calmness that comes when there’s no fighting or war, everything coexisting in perfect harmony and freedom.\,
},
{
role: "user",
content:
\Translate "${text}" to English. If the word the was already in English respond with its definition. Respond just with the translation or definition.\,
},
],
model: "gpt-4",
});
return completion.choices[0].message.content || "Translation Error";
}
ואם תתחברו לואל טאון (זה בחינם) תוכלו לשנות את הקוד ולהריץ את הסקריפט מתוך דף הסקריפט בכתובת:
https://www.val.town/v/ynonp/translateToEnglishWithOpenAI
לא צריך מפתח API ל Chat GPT כי ואל טאון נותנים לכם במתנה.
בניית סקריפטים גדולים יותר על בסיס הפשוטים
אפשר לשלב כל סקריפט שאנחנו בונים ב val.town בסקריפטים אחרים. לדוגמה הסקריפט הזה:
import translateToEnglish from "https://esm.town/v/ynonp/translateToEnglishWithOpenAI";
console.log(await translateToEnglish("מה המצב"));
או יותר מעניין, הסקריפט הזה שמייצג בוט לטלגרם:
import { OpenAI } from "https://esm.town/v/std/openai";
import { telegramSendMessage } from "https://esm.town/v/vtdocs/telegramSendMessage?v=5";
import translateToEnglishWithOpenAI from "https://esm.town/v/ynonp/translateToEnglishWithOpenAI";
export const telegramWebhookEchoMessage = async (req: Request) => {
// Verify this webhook came from our bot
if (
req.headers.get("x-telegram-bot-api-secret-token")
!== Deno.env.get("TELEGRAM_WEBHOOK_SECRET")
) {
return new Response("Not Allowed", { status: 401 });
}
// Echo back the user's message
const body = await req.json();
const text: string = body.message.text;
const chatId: number = body.message.chat.id;
const translated = await translateToEnglishWithOpenAI(text);
if (translated) {
await telegramSendMessage(
Deno.env.get("TELEGRAM_BOT_TOKEN"),
{ chat_id: chatId, text: translated },
);
} else {
await telegramSendMessage(
Deno.env.get("TELEGRAM_BOT_TOKEN"),
{ chat_id: chatId, text: "Translation error" },
);
}
return Response.json("ok");
};
את הסקריפטים שאתם כותבים שם אפשר להריץ בתגובה לאימייל שנשלח, בצורה מחזורית (cron job) או בתגובה לבקשת HTTP (כמו הבוט לטלגרם פה).
מה עוד1 418
היום למדתי: חיפוש פודקסטים ב podcast index
ל val town יש את אחד ה AI-ים הכי חמודים שפגשתי - לא רק שהוא כותב את הקוד בשבילי, אפשר גם להריץ ולעשות Deploy בלחיצת כפתור, וכל זה בחינם. וכן אני יודע שגם רפליט מספקים שירות דומה ואפילו עם תמיכה בהרבה יותר שפות, אבל רפליט לאחרונה מרגיש מסורבל מדי.
בחזרה ל val town בשביל לשחק עם הבוט ביקשתי שיכתוב פונקציה לחיפוש פודקסטים ב podcast index. למרבה ההפתעה התוצאה עבדה בניסיון הראשון, כולל הטריק מה API שלהם עם ה sha1 למפתח. זה הקוד:
import crypto from "https://esm.sh/crypto-js";
export default async function searchPodcasts(query: string) {
const apiKey = Deno.env.get("PODCAST_INDEX_API_KEY");
const apiSecret = Deno.env.get("PODCAST_INDEX_API_SECRET");
// Generate authentication headers as per Podcast Index API requirements
const currentTime = Math.floor(Date.now() / 1000);
const hashString = apiKey + apiSecret + currentTime;
const hash = crypto.SHA1(hashString).toString();
const headers = {
"X-Auth-Key": apiKey,
"X-Auth-Date": currentTime.toString(),
"Authorization": hash,
"User-Agent": "ValTownPodcastSearch/1.0",
};
try {
const response = await fetch(\https://api.podcastindex.org/api/1.0/search/byterm?q=${encodeURIComponent(query)}\, {
method: "GET",
headers: headers,
});
if (!response.ok) {
throw new Error(\HTTP error! status: ${response.status}\);
}
const data = await response.json();
return data.feeds.map(podcast => ({
title: podcast.title,
description: podcast.description,
url: podcast.url,
artwork: podcast.artwork,
}));
} catch (error) {
console.error("Podcast search error:", error);
return [];
}
}
console.log(await searchPodcasts("syntax"));
ואם יש לכם 5 דקות תוכלו גם ליצור חשבון ב val town ולהריץ אותו ישר מהממשק שלהם בקישור:
https://www.val.town/v/ynonp/righteousLimeGalliform
רק שימו לב שצריך קודם ליצור את שני משתני הסביבה PODCAST_INDEX_API_KEY ו PODCAST_INDEX_API_SECRET דרך הממשק של val town לחשבון שלכם בקישור:
https://www.val.town/settings/environment-variables
ובשביל לקבל את המפתחות יש ליצור חשבון ב podcast index api:
https://api.podcastindex.org/
נ.ב. הממשק של podcast index די מדליק אפשר למצוא את כל המידע על פודקסט, רשימות הפרקים וגם לינקים לקבצי הקול של כל פרק.1 418
חידת Vue - איך עובד Scoped Style
נכתוב את הקוד הבא בקומפוננטת App.vue:
<script setup>
import Child from './Child.vue';
</script>
<template>
<p class="red">Hello World from App</p>
<Child />
</template>
<style scoped>
.red {
color: red;
}
</style>
וזה Child.vue:
<script setup></script>
<template>
<button class="red">
Hello World from Child
</button>
</template>
אפשר לראות את הדוגמה לייב בקישור הזה:
https://tinyurl.com/4nff62kp
עכשיו לשאלות - למה הטקסט בכפתור מופיע באדום? ואיך אפשר "לנתק" אותו מהעיצוב של App כדי שלא יקבל את הגדרת האדום משם?
שימו לב שהגדרות העיצוב הן בבלוק styled scoped ושזו לא הבעיה שמתוארת כאן:
https://www.tocode.co.il/blog/2021-10-watch-out-inherited-css, למרות שגם הבעיה בפוסט ההוא מעניינת.1 418
ואם בכל זאת אני רוצה להעביר משתנה ריאקטיבי ב Vue לילדים?
הקוד הבא ב Vue לא עובד, או לפחות לא עושה את מה שהתכוונתי. קומפוננטה עליונה:
<script setup>
import { ref } from 'vue'
import Child from './Child.vue';
const x = ref(10)
</script>
<template>
<Child :x="x" />
</template>
וקומפוננטת Child:
<script setup lang="ts">
const {x} = defineProps<{x: any}>();
function inc() {
x.value++;
}
</script>
<template>
<p>x = {{x}}</p>
<button @click="inc">+1</button>
</template>
נראה כאילו Child מקבל אוביקט ריאקטיבי x ומעלה את ערכו ב-1. בפועל העברת הפרמטר דרך props מעבירה את ה value של האוביקט הריאקטיבי, כלומר בתוך Child המשתנה x הוא מספר. למספר אין שדה .value ולכן הקוד נכשל.
מה בכל זאת אפשר לעשות?
ל Vue יש שני פיתרונות טובים למצב הזה: הראשון הוא ש Child ידווח על אירוע ו Parent יטפל באירוע וישנה את x, והשני זה מקרו בשם defineModel שבגדול מטפל בדינמיקה הזאת בשבילנו אבל מבחינת הקוד נראה לגמרי כמו העברת משתנה ריאקטיבי לקומפוננטה. שניהם פיתרונות טובים.
הפיתרון ש Vue לא רצו שניישם הוא לעטוף את המשתנה הריאקטיבי באוביקט. מוזר אבל עובד:
<script setup>
import { ref } from 'vue'
import Child from './Child.vue';
const x = ref({value: 10})
</script>
<template>
<Child :x="x" />
</template>
<script setup lang="ts">
const {x} = defineProps<{x: any}>();
function inc() {
x.value++;
}
</script>
<template>
<p>x = {{x.value}}</p>
<button @click="inc">+1</button>
</template>
ברור למה זה עובד - ref הוא רקורסיבי ולכן גם value שלו יהיה משתנה ריאקטיבי, אבל כשמעבירים אוביקט דרך props ויו לא מבטל את הריאקטיביות של השדות הפנימיים בצורה רקורסיבית ולכן Child יכול לשנות את המידע.
מה שלדעתי מעניין בדוגמה הזאת הוא כמה קל "לפתור" את הבעיה בצורה לא נכונה אולי אפילו בלי לשים לב שזה פיתרון לא נכון. שתי הדרכים ה"נכונות" לפיתרון דורשות היכרות עם מנגנונים נוספים של vue, ודווקא הפיתרון העקום הוא בעצם הפשוט ביותר, מהבחינה שהוא לא דורש עוד מנגנונים.
הלקח שלי מהדוגמה הזאת הוא שרק בגלל שפיתרון מסוים הוא היחיד שאני רואה זה עדיין לא אומר שזה הפיתרון הנכון. בשביל להגיע לפיתרון הנכון צריך לפעמים להמשיך לקרוא בתיעוד, להתלבט בין כמה אפשרויות ולהבין את היתרונות והחסרונות של כל אפשרות.1 418
מאחר ו vercel תומך ב nuxt אין לנו בעיה לעשות deploy בלחיצה אחת ולקבל את כל הפינוקים של vercel. בסיס הנתונים הוא עדיין neon וגם הוא שמור בענן.
1 418
תבנית פרויקט: nuxt, drizzle, auth0
אתמול פירסמתי פה תבנית לפרויקט React שמשתמש ב Next.js ומכיל קומפוננטות צד שרת, משיכת מידע מבסיס נתונים וניהול משתמשים עם auth0. היום נראה את החלק השני של הפוסט ונבנה את אותה תבנית עבור nuxt ליישומי vue. הקוד כאן:
https://github.com/ynonp/nuxt-drizzle-auth0-demo
בואו נראה מה יש בריפו.
ניהול משתמשים
ניהול המשתמשים במערכת מתחבר ל auth0 כדי שאנחנו לא נצטרך להתאמץ, בדיוק כמו בדוגמת ה next. זה אומר שבצד שלנו אנחנו צריכים לטפל באירוע של משתמש שהתחבר או התנתק, ונוכל להשתמש בפונקציות של auth0 כדי להבין אם יש משתמש מחובר.
הקוד מתחיל בקובץ
server/routes/auth/auth0.get.ts:
export default defineOAuthAuth0EventHandler({
async onSuccess(event, { user, tokens }) {
await setUserSession(event, {
user: {
id: user.sub
}
})
return sendRedirect(event, '/hello')
},
// Optional, will return a json error and 401 status code by default
onError(event, error) {
console.log(\error in login\);
return sendRedirect(event, '/')
},
})
זה הקוד שמטפל באירועים שמגיעים מ auth0 כשמשתמש מתחבר למערכת. בצד של nuxt אני לוקח את user.sub שזה מזהה המשתמש ושותל אותו ב session כדי שנוכל לגשת אליו מהקומפוננטות ומנתיבי צד שרת.
הקומפוננטה הבאה בקובץ pages/hello.vue כבר כוללת קוד שמאפשר חיבור למערכת:
<script setup>
import People from '../components/People.vue';
const { loggedIn, user, session, fetch, clear } = useUserSession()
</script>
<template>
<div v-if="loggedIn">
<h1>Welcome {{ user.id }}!</h1>
<p>Logged in since {{ session.loggedInAt }}</p>
<button @click="clear">Logout</button>
<People />
</div>
<div v-else>
<h1>Not logged in</h1>
<a href="/auth/auth0">Login with Auth0</a>
</div>
</template>
בשביל להתחבר למערכת צריך רק לעבור לנתיב /auth/auth0 ובשביל להתנתק צריך להפעיל את הפונקציה clear שמנקה את ה Session. כל הקוד הזה משתמש במודול auth-utils של nuxt.
בשביל החיבור ל Auth0 יש להגדיר את משתני הסביבה שלהם בקובץ ה .env. אלה המפתחות שעליכם להגדיר בקובץ כדי שהפרויקט יעבוד:
NUXT_SESSION_PASSWORD=
NUXT_OAUTH_AUTH0_CLIENT_ID=
NUXT_OAUTH_AUTH0_CLIENT_SECRET=
NUXT_OAUTH_AUTH0_DOMAIN=
DATABASE_URL=
חיבור לבסיס נתונים
החיבור לדריזל משתמש בדיוק באותו קוד שהראיתי אתמול לגבי next. בצד של nuxt העברת המידע מבסיס הנתונים לפרונט אנד קצת שונה:
1. יש להגדיר נתיב צד שרת שימשוך את הנתונים מבסיס הנתונים.
2. יש להפעיל את הפונקציה useAsyncData של nuxt מתוך הקומפוננטה וממנה למשוך את המידע מהנתיב שיצרנו.
בשביל הסעיף הראשון אני יוצר קובץ server/routes/api/people.get.ts עם התוכן הבא:
import { usersTable } from '@/db/schema';
import { db } from "@/db/drizzle";
export default eventHandler(async (event) => {
try {
const res = await requireUserSession(event)
const users = await db.select().from(usersTable);
return users;
} catch (err) {
return [];
}
})
אם המשתמש מחובר הוא יקבל את רשימת המשתמשים מבסיס הנתונים ובשביל המשחק אם המשתמש לא מחובר נחזיר לו רשימה ריקה.
הקומפוננטה שמשתמשת בנתיב זה שמורה בקובץ components/People.vue וזה הקוד שלה:
<script setup lang="ts">
const { data, status, error, refresh } = await useAsyncData(
'people',
() => $fetch('/api/people', {
credentials: 'include',
headers: useRequestHeaders(['cookie']),
})
)
</script>
<template>
<h1>People. Status = {{ status }}</h1>
<ul>
<li v-for="person in data">{{ person.email }}</li>
</ul>
</template>
<style lang="css" scoped>
</style>
השימוש ב fetch מתוך useAsyncData נראה כמו בקשת רשת אבל למעשה זו בקשה מקומית כי הקוד רץ בצד השרת ב SSR. כשהקוד יישלח לדפדפן הוא יכלול כבר את התשובה (כלומר את רשימת המשתמשים). נשים לב שאני חייב להעביר את ה cookie מהדפדפן בשביל לשמור על ה Session. שאר הקוד די פשוט הוא לוקח את רשימת המשתמשים מהנתיב שהגדרנו ומציג את כתובות המייל שלהם על המסך.1 418
מה עושים עם הקוד שלא מבינים?
דילמה אמיתית. והיא תמיד היתה.
אחד המקומות המפחידים ביותר מתכנתים נקרא Tutorial Hell. להיות ב Tutorial Hell אומר שאתה תקוע לראות מדריכים שמסבירים איך לכתוב דברים אבל לא משנה כמה מדריכים תראה לא תצליח לכתוב קוד חדש בעצמך. היום כש ChatGPT יכול לכתוב לך מדריך שפותר באופן מיידי כל בעיה בקוד ה Tutorial Hell לא נראה כל כך רע, מה שאומר שאנחנו נשארים בו הרבה יותר זמן.
הגורם ל Tutorial Hell הוא המעגל הזדוני של הצלחה מדומיינת. אני יכול לגרום למערכת לעשות מה שאני רוצה באמצעות ביצוע הוראות שקראתי במדריך באינטרנט, אפילו אם אני לא מבין את ההוראות עצמן. מספיק ללכת צעד צעד עם המדריך (או בעולם של היום לתת ל AI לשכתב חלקים מהמדריך תוך כדי תנועה אם דברים לא עובדים) ויש לי מערכת. ובכל זאת מעניין לשאול, מה עושים עם כל הקוד הזה שאנחנו קוראים, מעתיקים ולא מבינים?
בואו נחלק את התשובה לפי סוג הפרויקט. בפרויקט לימודי המטרה שלנו מראש היתה ללמוד איך דברים עובדים, ולכן נשמע הגיוני להשתמש כמה שפחות ב Tutorials. קוד שאני לא מבין הוא חסר ערך בקונטקסט הזה, כי ממילא אני בונה את המערכת רק בשביל ללמוד איך דברים עובדים. כיוון טוב לפרויקט לימודי יהיה לכן לקרוא טוטוריאלס או תשובות של Chat GPT רק במידה וזה נותן כיוון למאמרים יותר מעמיקים על אותו נושא, אחרי זה ללכת לקרוא את התיעוד והמידע היותר מעמיק, ובסוף לכתוב את הקוד בעצמך בלי להסתכל בטוטוריאל ובלי להעתיק קוד. אם דברים לא עובדים אפשר לחזור לקרוא במדריך אבל המטרה בסוף תהיה להיות מסוגלים לכתוב את הכל מהראש וכמובן להסביר לעצכם מה המשמעות ומה עושה כל שורה.
בפרויקט פרודקשן התשובה נראית יותר מורכבת, כי לכאורה שימוש ב Tutorial חוסך זמן לימוד ומאפשר לסיים משימות מהר יותר. אני מכיר אפילו חברות שמתגמלות מפתחים לפי כמה משימות הם מסיימים בחודש ולכן מתכנתים שרוצים בונוס רק ירוויחו מהעתקת קוד שהם לא מבינים לפרויקט פרודקשן. אבל גם פה כשעוצרים לחשוב על זה אנחנו מבינים שיש לנו עבודה חשובה יותר מהעתקת קוד. בפרויקט פרודקשן יש שני אתגרים שעדיין דורשים התערבות אנושית:
1. רגישות לטעויות או לפגיעה בנתוני פרודקשן.
2. התאמה לשיטות העבודה הקיימות בפרויקט, הימנעות מכפל קוד ויצירת אבסטרקציות חדשות כדי שהקידוד בהמשך יהיה מהיר יותר.
לכן גם בפרויקט פרודקשן העתקת קוד בלי להבין מ Tutorial או מ Chat GPT פוגעת בפרודוקטיביות לטווח ארוך.
הפרויקט היחיד שאני מרגיש בנוח להעתיק אליו קוד שאני לא מבין הוא פרויקט הוכחת יכולת (Proof of Concept), כשאני רוצה לבנות משהו מהר כדי להראות ללקוח או לחבר, או להבין לעצמי מה היכולות של ספריה מסוימת. במצב כזה אני עובד עם ספריות וכלים שאני לא הכי מכיר ורק רוצה לראות אם הם יכולים לפתור לי את הבעיה ואם כדאי לי בכלל ללמוד אותם, ולכן אני מרגיש יותר נוח להעתיק קוד שבאותו רגע אני לא מבין, מתוך הבנה שאם הכלים האלה יעמדו במבחן אצטרך ממילא ללמוד אותם לעומק ואז כבר אזרוק את קוד ה POC כדי לבנות את המנגנון מחדש במערכת האמיתית שלי.
נ.ב. הפוסט הזה מדבר ספציפית על קוד שאני מעתיק בלי להבין. הרבה פעמים אני אשתמש ב Chat GPT כדי לכתוב קוד שהייתי יכול לכתוב לבד ושאני מבין בדיוק איך הוא אמור להיראות, לדוגמה כשאני מבקש מ Chat GPT לכתוב את רשימת כל הימים בשבוע בתור מערך ב JavaScript ברור שהייתי יכול לכתוב את זה לבד אבל הוא חסך לי הקלדה. כלי ה AI הם כלים מדהימים ואני שמח להשתמש בהם כמה שיותר, כל עוד זה באמת תורם לאיכות הקוד ומהירות הפיתוח.
1 418
הפחד מעבודה לא מושלמת
הפחד מעבודה לא מושלמת הוא אמיתי. לפעמים הוא גורם לנו לוותר על פרויקט לפני שאנחנו אפילו מתחילים (כי אין לי זמן עכשיו לסיים את זה או להשקיע מספיק), באמצע העבודה (חשבתי שזה יהיה רעיון טוב אבל בעצם זה לא שווה את המאמץ, יש לי רעיון חדש טוב יותר) ואפילו אחרי שיש משהו מוכן וצריך רק להראות אותו (זה לא מספיק טוב).
המחשבות שגורמות לפחד נעות סביב:
1. יש רק הזדמנות אחת וחבל לי לפספס אותה.
2. אם אזרוק את מה שבניתי תהיה לי יותר מוטיבציה לנסות שוב.
3. חבל לבזבז זמן או מאמץ על פרויקט שלא יצליח, עדיף להתמקד בדברים אחרים שאולי כן יהיו מושלמים.
4. אנשים סביבי שיראו פרויקט בינוני יסיקו מזה עליי ועל הרמה שלי כמפתח.
אף פעם לא קל לשנות סיפורים בראש אבל הנה כמה רעיונות ששווה לנסות לאמץ במקום:
1. החיים מלאים הזדמנויות, וההזדמנויות באות לעתים יותר קרובות לאנשים שמוכנים להתאמץ בשביל לנצל אותן.
2. הפורטפוליו שלי נמצא תמיד בצמיחה. כל פרויקט הוא הבסיס לפרויקט הבא.
3. חבל לבזבז עוד שעה בנטפליקס או בפייסבוק, או אפילו בקורס. הלימוד הכי טוב הוא דרך עשייה.
4. כדי לבנות פרויקט טוב עליי לבנות קודם פרויקטים בינוניים ואפילו גרועים.
אימוץ תפיסת עולם של צמיחה יכול לפתוח את הדלת ליותר עשייה ולעשייה טובה יותר.
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
