1 420
订阅者
无数据24 小时
+37 天
-430 天
帖子存档
1 420
# אני יודע שעוד אתחרט על זה
כשאני כותב פונקציה של 200 שורות.
כשאני כותב מספר עם משמעות מיוחדת ישירות בתוך הקוד, בלי להגדירו כקבוע.
כשאני כותב קוד שאין לי איך לבדוק אותו.
כשאני שומר את המפתח בתוך הקוד.
כשאני מוותר על כתיבת טיפוסי משתנים או תיעוד.
כשאני מעתיק קטע קוד מ Chat GPT או Stack Overflow, בלי להבין אותו עד הסוף.
אני יודע שעוד אתחרט על זה, אבל אני עדיין לא יודע איך לפתור את הבעיה בצורה נכונה. הערת הקסם
TODO נועדה להדגיש נקודה זו בדיוק. האם הייתי מעדיף לוותר על ה Todos ולכתוב הכל נכון מהפעם הראשונה? ברור. אבל אז כנראה שלעולם לא אסיים את הפרויקט.
אני יודע שעוד אתחרט על זה. וכשזה יקרה אשמח לשכתב. עד אז צריך להתקדם.1 420
2. סנן מהם רק את אלה עם התווית reader ושיש להם מאפיין בשם email עם הערך שהגיע מהמשתנה email.
3. שמור את כל הצמתים שמצאת במשתנה פנימי של השאילתה שנקרא reader. עוד מעט נחזור אליו.
4. חזור לכל הצמתים.
5. סנן מהם רק את אלה שיש להם תווית category, מאפיין name עם הערך מהמשתנה categoryName.
6. שמור את התוצאה במשתנה הפנימי של השאילתה שנקרא category.
7. צור קשת חדשה בשם
subscribed_to.
8. חבר את הקשת לצומת שהיה שמור במשתנה השאילתה reader, כך שהקשת תצא מהצומת.
9. חבר את הקשת לצומת category כך שהקשת תיכנס לצומת.
הפקודה iterate בסוף מבצעת את הטיול בגרף, כמו next ו toList מדוגמאות קודמות.
מעניין לדמיין איך שאילתה כזאת היתה נראית בסייפר, רק לצורך השוואה:
MATCH (r:Reader{email: $email})
MATCH (c:Category{name: $name})
MERGE (r)-[:SUBSCRIBED_TO]-(c)
RETURN r, c;
מעבר להבדל באורך, יש גם הבדל משמעותי בשיטת העבודה - בסייפר אני מתאר מה אני רוצה שיקרה, בגרמלין אני מתאר איך אני רוצה שזה יקרה. גרמלין נותן לי יותר שליטה אבל על חשבון קוד יותר ארוך ויותר מקום לטעויות.
בשביל לחבר פוסט לקטגוריה אני משתמש בשאילתה דומה:
g
.V()
.has("post", "slug", P.within("first", "second"))
.as("posts")
.V()
.has("category", "name", "spam")
.as("spam")
.addE("belongs_to")
.from("posts")
.to("spam")
.iterate()
או בעברית - תתחיל עם הפוסטים שה slug שלהם הוא first או second, תמצא קטגוריה בשם spam ותיצור קשת בין הדברים. כך אחבר את הפוסט השלישי לקטגוריה gremlin:
// connect hello-gremlin post to gremlin category
g
.V()
.has("post", "slug", P.within("hello-gremlin"))
.as("posts")
.V()
.has("category", "name", "gremlin")
.as("cat")
.addE("belongs_to")
.from("posts")
.to("cat")
.iterate()
ולחלק המעניין - איך מוצאים את כל הפוסטים שמשתמש מסוים צריך לקבל? פשוט הולכים עם הקשתות:
def subscriberPosts(g: GraphTraversalSource, email: String): util.List[util.Map[AnyRef, Nothing]] =
g
.V()
.has("reader", "email", email)
.out("subscribed_to")
.in("belongs_to")
.valueMap()
.toList
מתחילים עם צמתים שיש להם תווית reader, יוצאים מהם דרך הקשת subscribed_to ואז ממשיכים עוד צעד בכיוון ההפוך מקשת שנכנסת אליהם בשם belongs_to. ושימו לב להבדל מול סייפר:
MATCH (r:Reader)-[:SUBSCRIBED_TO]-(c:Category)
MATCH (p)-[:BELONGS_TO]-(c)
RETURN p;
## יצירת "קטגוריית על" ורישום אליה
אתגר אחרון הוא שאילתות רקורסיביות, שזה אחד הפיצ'רים המדליקים בבסיסי נתונים גרפיים. במקרה שלנו נוכל ליצור קטגוריית-על שהיא קטגוריה שיש בתוכה קטגוריות אחרות, ואז להמשיך וליצור קטגוריית-על-על וכן הלאה. הקוד הזה יוצר קטגוריה בשם all ומחבר את כל הקטגוריות אליה:
g
.V()
.has("category", "name", "all")
.as("all")
.V()
.hasLabel("category")
.not(has("name", "all"))
.as("oldCategories")
.addE("belongs_to")
.from("oldCategories")
.to("all")
.iterate()
והקוד המלהיב באמת הוא הפונקציה הבאה, שמחזירה את כל הפוסטים מכל הקטגוריות שמשתמש מנוי אליהן, ובצורה רקורסיבית כדי לטפל בקטגוריות-על:
def subscriberPostsRecursive(g: GraphTraversalSource, email: String) =
g
.V()
.has("reader", "email", email)
.out("subscribed_to")
.repeat(in("belongs_to"))
.until(hasLabel("post"))
.valueMap()
.toList
הלולאה הרקורסיבית מיוצגת על ידי הצעדים repeat ו until - אנחנו ממשיכים לסרוק את הגרף לאורך קשתות belongs_to עד שמגיעים לפוסט, וזה בסדר אם נעבור הרבה קטגוריות בדרך.
עדיין מוקדם להגיד מה דעתי על גרמלין. הוא בטוח הרבה יותר קשה ללמידה בהשוואה לסייפר, יש יותר מילות קוד וקשר הדוק בינו לבין שפת התכנות. השליטה והירידה לפרטים של גרמלין יכולות להרגיש כמו סירבול מיותר, אבל גם מעודדות הבנה ומפחיתות את תחושת ה"קסם". כשגרמלין לא עובד זה מרגיש שיש יותר אופציות לדבג ולהבין מה שבור שם.
אם יש לכם ניסיון עם אחת מהשפות ורוצים להוסיף ולשתף אשמח לשמוע בתגובות, ואם לא אל דאגה עם הזמן אמשיך לשתף רשמים על שתיהן.1 420
הדבר החשוב השני כאן הוא ששאילתה בנויה מצעדים, וכל פונקציה שאני מפעיל מוסיפה צעד למסלול הניווט בגרף. אז אם נתרגם את הצעדים לעברית נקבל:
1. צור צומת חדש בגרף עם התווית post ולך אליו.
2. בצומת בו אתה נמצא הגדר מאפיין בשם slug עם הערך ששמור במשתנה slug.
3. בצומת בו אתה נמצא הגדר מאפיין בשם title עם הערך מהמשתנה title.
4. בצומת בו אתה נמצא הגדר מאפיין publishedAt עם הערך שהוא התאריך שקיבלנו במשתנה publishedAt.
הפקודה האחרונה, next, מביאה אותנו לרעיון השלישי החשוב לגבי גרמלין. כל מה שבניתי עד עכשיו הוא תיאור של מסלול ניווט בגרף. מסלול ניווט כזה יוצר סידרה של תוצאות (במסלול שלי סידרה בגודל 1, במקרה הכללי יכולות להיות יותר תוצאות). רק אם אני "מושך" תוצאה או יותר מהסידרה אז משהו בכלל יקרה, והפקודה
next מושכת את התוצאה הראשונה מהסידרה. זה נראה כמו סיבוך מיותר אבל בעצם זה כח-על מאוד חשוב של גרמלין, כי הוא יאפשר לי להגדיר שאילתות ולהעביר אותן כפרמטרים לשאילתות אחרות כדי ליצור שאילתות מורכבות.
אם זה היה ברור אפשר להמשיך לעוד שתי פונקציות כדי ליצור קטגוריה ומנוי:
def createCategory(g: GraphTraversalSource, name: String): Vertex =
g.addV("category")
.property("name", name)
.next()
def createSubscriber(g: GraphTraversalSource, email: String): Vertex =
g.addV("reader")
.property("email", email)
.next()
ומתוך ה main אני משתמש בפונקציות בצורה הבאה:
@main def main() =
val graph = TinkerGraph.open
val g = traversal.withEmbedded(graph)
createPost(g, "first", "first post", "2023-01-24T06:00:00")
createPost(g, "second", "second post", "2023-01-25T06:00:00")
createPost(g, "hello-gremlin", "Hello World in Gremlin", "2023-04-02T06:00:00")
createCategory(g, "spam")
createCategory(g, "gremlin")
createSubscriber(g, "foo@demomail.com")
createSubscriber(g, "bar@demomail.com")
createSubscriber(g, "buz@demomail.com")
## שאילתות על צמתים
איך נדע שהיצירה עבדה? ננסה למשוך מידע מהגרף. הנה למשל הדפסת כל המנויים:
println(g.V().hasLabel("reader").valueMap().toList)
בעברית הצעדים הם:
1. קח את כל הצמתים בגרף
2. סנן מהם רק את אלה שמכילים את התווית reader
3. קח מכל צומת מפה של כל המאפיינים והערכים שלהם
הפקודה האחרונה, toList, מחליפה את next ומבקשת למשוך את כל התוצאות מהסידרה ולהחזיר רשימה שלהן. השורה תדפיס:
[{email=[foo@demomail.com]}, {email=[bar@demomail.com]}, {email=[buz@demomail.com]}]
אגב לכל צומת יש גם מאפיינים שבסיס הנתונים נותן לו. אפשר להדפיס גם אותם אם אני מעביר ערך true לפונקציה valueMap:
println(g.V().hasLabel("reader").valueMap(true).toList)
ואז מקבלים:
[{id=16, label=reader, email=[foo@demomail.com]}, {id=18, label=reader, email=[bar@demomail.com]}, {id=20, label=reader, email=[buz@demomail.com]}]
באותו אופן אני יכול להדפיס גם את כל הפוסטים:
// Get all blog posts
println(g.V().hasLabel("post").valueMap().toList)
ואפשר גם להדפיס רק צמתים עם מאפיינים מסוימים למשל רק קטגוריות בשם gremlin:
println(g.V().has("category", "name", "gremlin").valueMap().toList)
או רק פוסטים שפורסמו בטווח תאריכים מסוים:
println(
g.V()
.has("post", "publishedAt",
P.between(
LocalDateTime.parse("2023-04-01T00:00:00", formatter),
LocalDateTime.parse("2023-05-01T00:00:00", formatter),
))
.valueMap().toList)
## חיבור צמתים בקשתות ורישום כמנוי לקטגוריה
עכשיו שיש לנו קטגוריות וקוראים, בואו נחבר ביניהם כדי שנוכל לגלות על פוסטים חדשים שמתפרסמים (וכן עוד מעט נחבר גם את הפוסטים לקטגוריות). זו הפונקציה:
def subscribeToCategory(g: GraphTraversalSource, email: String, categoryName: String): GraphTraversal[Vertex, Edge] =
g
.V()
.has("reader", "email", email)
.as("reader")
.V()
.has("category", "name", categoryName)
.as("category")
.addE("subscribed_to")
.from("reader")
.to("category")
.iterate()
בתרגום לעברית זה יהיה:
1. קח את כל הצמתים.1 420
# צעדים ראשונים עם גרמלין
גרמלין היא שפת שאילתות לבסיסי נתונים גרפיים. היא השלישית שאני סוקר כאן בבלוג אחרי פוסט על דטומיק ודטהלוג ופוסט על סייפר ו neo4j.
## מה מיוחד בגרמלין
ההבדל המרכזי בין גרמלין לסייפר הוא האינטגרציה בין שפת השאילתות לשפת התכנות. שאילתה בסייפר היא פשוט בלוק של טקסט שיש בו הוראה לבסיס הנתונים. בסיס נתונים מריץ את השאילתה ומחזיר ערך, לדוגמה שאילתה זו תחזיר את כל הצמתים עם התווית Person:
MATCH (p:Person)
RETURN p;
גרמלין היא שפת שאילתות שבנויה בתור ספריה לשפת התכנות שלכם ומשולבת עם שפת התכנות. בדרך כלל דוגמאות גרמלין כתובות בגרובי או ב Java, אבל אני כותב את הקוד כאן בסקאלה כי אני בדיוק לומד אותה גם. אותה שאילתה בגרמלין תהיה:
g.V().hasLabel("Person").toList()
זה לא טקסט שאני צריך לכתוב בתוך מרכאות ולשלוח לבסיס הנתונים אלא ממש הפעלה של פונקציות. יותר מזה בגרמלין מודל העבודה הוא אימפרטיבי ושאילתה ממש מגדירה מסלול בו אנחנו עוברים על הגרף. בואו נראה את השאילתות דרך אותן דוגמאות שכתבתי עליהן בפוסט על סייפר כדי שאפשר יהיה להשוות.
## מה אנחנו בונים
בשביל הדוגמה נבנה מודל שמייצג פוסטים בבלוג ואנשים שאמורים לקבל עדכונים על פוסטים חדשים דרך האימייל. כל פוסט שייך לקטגוריה מסוימת, אנשים יכולים להירשם כמנויים לקטגוריות ונרצה לקבל רשימה של כל הפוסטים מהקטגוריות שמשתמש מסוים מנוי אליהן, או רק כאלה שהתפרסמו בתאריך מסוים או מאז האימייל האחרון ששלחנו.
## יצירת צמתים עבור פוסטים, קטגוריות ומנויים
את תוכנית הדוגמה אני כותב בסקאלה ועם מימוש גרף שנקרא tinkergraph. זה גרף שנשמר בזיכרון ומממש את כל ה API של גרמלין. הוא מאוד נוח בכתיבת תוכניות בדיקה כי עכשיו כל תוכנית בדיקה מקבלת משהו שנראה לגמרי כמו בסיס נתונים. בשביל להתחיל לעבוד עם גרמלין וטינקרפופ מתוך תוכנית סקאלה עליי להתקין את הספריות באמצעות השורות הבאות בקובץ build.sbt:
ThisBuild / scalaVersion := "3.3.1"
libraryDependencies += "org.apache.tinkerpop" % "gremlin-core" % "3.7.0"
libraryDependencies += "org.apache.tinkerpop" % "tinkergraph-gremlin" % "3.7.0"
ובתוך התוכנית אני משתמש ב import-ים הבאים:
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.{GraphTraversal, GraphTraversalSource}
import org.apache.tinkerpop.gremlin.process.traversal.IO
import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
import org.apache.tinkerpop.gremlin.process.traversal.Operator.*
import org.apache.tinkerpop.gremlin.process.traversal.Order.*
import org.apache.tinkerpop.gremlin.process.traversal.P
import org.apache.tinkerpop.gremlin.process.traversal.Pop.*
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions.*
import org.apache.tinkerpop.gremlin.process.traversal.Scope.*
import org.apache.tinkerpop.gremlin.process.traversal.TextP.*
import org.apache.tinkerpop.gremlin.structure.Column.*
import org.apache.tinkerpop.gremlin.structure.Direction.*
import org.apache.tinkerpop.gremlin.structure.T.*
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*
import org.apache.tinkerpop.gremlin.structure.{Edge, Vertex}
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util
עכשיו אפשר להתחיל לעבוד. הפונקציה הראשונה נקראת createPost והיא יוצרת פוסט חדש בבסיס הנתונים. פוסט הוא פשוט צומת עם המאפיינים slug, title ותאריך פירסום. לכל צומת בגרף יש תווית ובעזרת התוויות קל לנו לשלוף צמתים מסוג מסוים. התווית של פוסט תהיה פשוט post. קוד? הנה:
def createPost(g: GraphTraversalSource, slug: String, title: String, publishedAt: String): Vertex =
g.addV("post")
.property("slug", slug)
.property("title", title)
.property("publishedAt", LocalDateTime.parse(publishedAt, formatter))
.next()
אז כבר אנחנו רואים שאני לא בונה שאילתה בתור איזה בלוק של טקסט ושולח אותו לדרייבר לתקשורת עם בסיס הנתונים אלא שבניית השאילתה היא אינטרקטיבית באמצעות ספריית גרמלין.1 420
# כך נראית בעיית הבנה
נתבונן בתוכנית הבאה בפייתון שמחשבת את ה sh256 של הקובץ
/etc/passwd:
import hashlib
from pathlib import Path
data = Path("/etc/passwd").read_text().encode("utf8")
print(hashlib.sha256(data).hexdigest())
וכן העובדה שהקוד עובד רק מחמירה את המצב.
התוכנית קוראת את הקובץ בתור טקסט, כלומר לוקחת את הביטים שרשומים בקובץ ובונה מהם מבנה נתונים של מחרוזת שיישמר בזיכרון של פייתון. אחרי זה התוכנית מקודדת את המחרוזת בחזרה לביטים ואותם שומרת במשתנה data. אם היתה בעיה בקידוד בזמן קריאת הקובץ, התוכנית היתה מתרסקת באמצע - אפילו שאף אחד לא היה צריך את הטקסט וכל מטרת הקריאה היא לקבל את הביטים עצמם.
אז איך מגיעים לכתוב קוד כזה? נו זה קל, מתחילים עם:
import hashlib
from pathlib import Path
data = Path("/etc/passwd").read_text()
print(hashlib.sha256(data).hexdigest())
מקבלים את הודעת השגיאה:
TypeError: Strings must be encoded before hashing
ועושים מה שהמחשב ביקש. מה שהמחשב לא יודע זה שאנחנו בכלל לא צריכים את ה String, ואפשר היה לדלג עליו ולקבל ישר את הביטים:
import hashlib
from pathlib import Path
data = Path("/etc/passwd").read_bytes()
print(hashlib.sha256(data).hexdigest())
בעיית הבנה נוצרת בדיוק כשאנחנו עוקבים אחרי הוראות במקום להבין את המשמעות של אותן הוראות. לא תמיד קל לזהות אותן, אבל כלי AI לפעמים יכולים לעזור. במקרה שלנו שלחתי את התוכנית המקורית ל Chat GPT וביקשתי הצעות איך לשפר אותה. זאת הגירסה המשופרת שקיבלתי:
import hashlib
from pathlib import Path
file_path = "/etc/passwd"
try:
with open(file_path, 'rb') as file:
file_content = file.read()
hash_value = hashlib.sha256(file_content).hexdigest()
print(f"SHA-256 Hash of '{file_path}': {hash_value}")
except FileNotFoundError:
print(f"Error: File '{file_path}' not found.")
except PermissionError:
print(f"Error: Permission denied while trying to read '{file_path}'.")
except Exception as e:
print(f"Error: {e}")
רוב השיפור לא היה רלוונטי לבעייה הספציפית שלי (כי Chat GPT), אבל דווקא בקריאת הקובץ הוא שם לב שעדיף לוותר על ההמרה לטקסט. הדבר החשוב הוא להשתמש בכל הכלים מסביבנו כדי להבין מה בדיוק אנחנו עושים ומה בדיוק אחרים עושים, ולאט לאט לסגור פערי הבנה כדי לשפר כל תוכנית שנכתוב בעתיד.1 420
# מהו דבר אחד שהיית משנה ב-
רוצים להעריך היכרות של מועמד עם טכנולוגיה מסוימת בעשר דקות שיחה? התחילו עם השאלה הזאת:
"מהו דבר אחד שהיית משנה ב X"
בואו ננסה את זה עם פייתון. התשובה הכי קלה היא "אני אוהב פייתון לא הייתי משנה בו כלום". ותשובה כזאת לא ממש עוזרת לנו להבין מה הבן אדם יודע על פייתון.
אנשים שירצו לעשות קצת יותר רושם כבר יתחילו לשתף דברים שמפריעים להם. בתחילת הדרך עם פייתון הם יוכלו להגיד "אני לא אוהב את זה שיש משמעות לאינדנטציה. הייתי מעדיף לכתוב קוד כמו שאני רוצה", או "אני לא אוהב את התחביר של List Comprehensions, אני מעדיף לכתוב לולאה רגילה כדי לבנות רשימות". משם אפשר להמשיך את השיחה ולדבר על היתרונות בכתיב ה List Comprehension ועל החשיבות של מבנה בקוד שלא משנה רשימה קיימת.
אלה שקצת יותר מכירים פייתון יוכלו להגיד "אני לא אוהב את מנגנון הסביבה הוירטואלית של פייתון", ואז אפשר לשאול למה ואיזה בעיות יש לכם עם סביבה וירטואלית, לדבר על הפיתרונות החלופיים ועל הבעיות שלהם.
עוד סוג מעניין של תשובות זה תלונות על ביצועים. וזה מעולה כי עכשיו אפשר לדבר על פרופיילינג של תוכנית פייתון, על ביצוע פעולות במקביל, על ההבדל בין async.io ל threads ועל ה GIL וכמה יהיה נחמד כשסוף סוף יורידו אותו. אפשר להציע לבנות מודול ב C ולקרוא לו מפייתון ולבדוק אם המועמד או המועמדת שהתלוננו על ביצועים ניסו לעשות את זה.
כמובן שיחות כאלה יכולות לקחת אותנו למנגנון הטיפוסים של פייתון ולהשוות אותו לשפות דינמיות אחרות, לדבר על איכות האקוסיסטם, על הסוכר התחבירי שיכול להיות מבלבל או על הקושי לכתוב תוכנית שתואמת לכל מערכות ההפעלה. הדבר החשוב הוא לא על מה אתם בוחרים לקטר, אלא כמה חקרתם וכמה פיתרונות שונים מצאתם לאותו קושי, ומה היתרונות והחסרונות של כל פיתרון.
ואולי הדבר הכי חשוב כאן הוא ההבנה שלא צריך לחכות לראיון עבודה כדי לשאול את עצמנו את השאלה הזאת. "מה הדבר שהייתי משנה בפייתון" הוא נקודת התחלה מצוינת כדי לחקור טוב יותר את השפה ולגלות שיטות עבודה חדשות.
1 420
# מה לעשות כשמישהו מציב לך אולטימטום
מצטט מSoatok כי העצה מושלמת, תרגום שלי:
> אם אני צריך לבחור רק עצה אחת לתת לכל מי שאי פעם עומד מול אולטימטום שהציבו לו גורמי סמכות היא תהיה "לעולם אל תבחרו להסכים לאולטימטום". אם המעסיק שלכם אומר שעליכם לעבור לעיר אחרת או להתפטר, המהלך הטוב ביותר, בסופו של דבר, יהיה לעזוב.
ההגיון מאחורי העצה הוא שאם תסכימו לאולטימטום אתם נכנסים לסיטואציה שיהיה קשה מאוד לצמוח ולהתפתח ממנה בהמשך. ספציפית הפוסט מדבר על חברה שמכריחה את העובדים לחזור למשרד, כולל עובדים שברור להם שהם יותר פרודוקטיביים בעבודה מהבית. עובד שיסכים לכזה אולטימטום ויבחר לעבוד בצורה פחות פרודוקטיבית יגרום לעצמו נזק לקריירה בטווח הרחוק.
ועצה זו גם מסתדרת על הניסיון האישי שלי - כל פעם שהסכמתי לדרישות של לקוח שנראו לי לא הגיוניות, זה הכניס אותי לפרויקט שהיה לי קשה לצאת ממנו ולא נתן לי מספיק ערך. יותר מזה, מספיק פעמים ראיתי חברים שהתווכחו עם הלקוחות ולא הסכימו לקבל דרישות לא הגיוניות, כולל דרישות דומות לאלה שאני הסכמתי לקבל, ובסופו של דבר בפעמים שהושגה פשרה גם הדינמיקה בתוך הפרויקט היתה טובה יותר.
אולטימטום מכתיב דינמיקה. דינמיקה משפיעה על איכות הפרויקט והתוצאה. ואיכות העבודה (במצטבר) היא שבונה קריירה.
מצאו לקוחות טובים יותר היא קריאה חשובה, ולפעמים עלינו להתעקש כדי להוציא את הצדדים היותר טובים מהלקוחות והמעסיקים שלנו.
1 420
# פחד הוא מדריך קריירה גרוע ממש
הבעיה עם פחד בתור מנטור היא שאנחנו פוחדים מהדברים הלא נכונים, לדוגמה-
הרבה אנשים פוחדים מטיסות, למרות שיש יותר סיכוי להיפגע בתאונת דרכים באוטו.
הרבה אנשים פוחדים מחיסון בזריקה, למרות שאין להם בעיה לקבל את אותו חומר בבליעה. שלא לדבר על זה שהרבה פעמים המחלה ממנה החיסון מגן גרועה בהרבה.
וזה פוגש אותנו גם בקריירה - הרבה אנשים פוחדים לדבר מול קהל או לספר על פרויקטים שבנו, למרות שלשמור רעיונות והישגים בבטן זה הרבה יותר מסוכן. הרבה אנשים פוחדים להישאר בלי עבודה, כשבעצם הרבה יותר מסוכן להישאר במקום ולהתעורר חמש שנים מאוחר מדי. הרבה אנשים פוחדים מללמוד את הדבר הלא נכון, כשבעצם הרבה יותר מסוכן לא ללמוד כלום.
מקרה קלאסי של פחד הוא אותה שכירה שחולמת להיות עצמאית אבל פוחדת שלא תרוויח מספיק כסף בתור עצמאית ולכן לא מנסה. התמודדות בריאה עם פחד כזה תהיה להבין מה זה לעבוד בתור עצמאית, לזהות ערוצי הכנסה אפשריים (ועדיף אפילו להתחיל להרוויח מהם בקטנה באמצעות השקעת מספר שעות בשבוע), ולייצר תוכנית מסודרת של סיכויים וסיכונים מתוך הבנת השטח. התמודדות לא בריאה היא לקטר לחברים שלהיות עצמאית זה מפחיד.
לפחד יש מקום. הוא עוזר לנו להבין את הגבולות שלנו ואת האמונות שלנו. דרך הפחד אני יכול למצוא מקומות ששווה לחקור אותם, בצורת "מעניין למה זה כל כך מפחיד אותי", "מעניין מה חושבים אנשים שלא פוחדים מהדבר הזה", או אפילו "מעניין איזה סוגי מחשבות אוכל לאמץ כדי לפחד יותר או פחות מהדבר הזה".
אבל בואו לא נתבלבל. את ההחלטות הרציניות של החיים כדאי לקבל אחרי מחקר מבוסס ושקילת האפשרויות והסיכונים. הימנעות מפעולות מפחידות הרבה פעמים רק מחמירה את המצב.
1 420
# אין לי מושג איך לגשת לזה
בהתמודדות עם בעיה, "אין לי מושג איך לגשת לזה" הוא התחלה מצוינת. ההמשך צריך להיות "אבל אני מוכן לנסות".
כי שני המשפטים האלה יחד מובילים אותנו קדימה-
1. אין לי מושג איך לגשת לזה, אבל אני מוכן לנסות את הרעיון הראשון ש Chat GPT יציע לי. גם אם זה לא יעבוד בטח אלמד משהו מהניסיון.
2. אין לי מושג איך לגשת לזה, אבל אני מוכן לנסות אז אשאל חברים, ואולי אקבל כמה רעיונות.
3. אין לי מושג איך לגשת לזה, אבל אני מוכן לנסות גם משהו שאף פעם לא ניסיתי קודם.
4. אין לי מושג איך לגשת לזה, אבל אני מוכן לנסות כי זה מספיק חשוב בשביל להתמודד עם האכזבה.
5. אין לי מושג איך לגשת לזה, אבל אני מוכן לנסות גם על חשבון דברים שאני כן יודע לעשות, כי שם הדרך קדימה.
אין לי מושג איך לגשת לזה. וזה בסדר גמור.
1 420
# עשר דקות עם סקאלה
אתם כבר יודעים שהתחלתי לא מזמן ללמוד סקאלה, ואחד הצעדים הראשונים שלי עם שפת תכנות הוא לממש דברים שאני כבר יודע לפתור רק בשביל לראות איך זה עובד. אז הנה פיתרון של Advent Of Code 2022 Day 2 בסקאלה, תוך שימוש נרחב ב match/case שלהם והחצים שנראה שמאפיינים את השפה.
## התרגיל
בתרגיל אנחנו מקבלים קובץ שנראה בערך כך:
A Y
B X
C Z
כשהעמודה השמאלית מייצגת מהלך במשחק "אבן נייר ומספריים" (A לאבן, B לנייר ו C למספריים) והעמודה הימנית בעלת משמעות משתנה. בחלק הראשון היא מייצגת את המהלך של היריב ובחלק השני היא מייצגת את התוצאה הרצויה (Y תיקו, X העמודה השמאלית מנצחת, Z העמודה השמאלית מפסידה). כל שורה מתורגמת לנקודות לפי ניצחון, תיקו או הפסד של העמודה הימנית ולפי המהלך שבוצע בעמודה הימנית. האתגר הוא לסכום את הנקודות.
## פיתרון בסקאלה
נקודת ההתחלה שלי היתה הפונקציה score שמקבלת שורה ומחזירה את הניקוד שלה:
def score(line: String): Int =
line match
case "A X" => 4
case "A Y" => 8
case "A Z" => 3
case "B X" => 1
case "B Y" => 5
case "B Z" => 9
case "C X" => 7
case "C Y" => 2
case "C Z" => 6
בסקאלה פונקציה מוגדרת עם def, לפרמטרים יש טיפוס ולפונקציה יש טיפוס החזר. אחרי הפקודה match יש רשימה של case-ים כשהם קבועים זה עובד כמו switch/case רגיל (וכן אפשר לעשות pattern matching מתוחכם כמו בשפות פונקציונאליות).
בחלק הראשון של התרגיל צריך רק לקרוא מקובץ ולסכום את התוצאה של score על כל שורה:
@main def main(): Unit =
val filename = "./src/main/scala/day2.txt"
var score1 = for (line <- Source.fromFile(filename).getLines)
yield score(line)
println(score1.sum)
את פקודת for בסקאלה אני קורא כמו List Comprehension של פייתון או for של אליקסיר, כשה yield קובע מה יוחזר. סך הכל המשתנה score1 יהיה רשימה של תוצאות של הפונקציה. וכן אפשר גם לוותר על כל תחביר הרכבת הרשימות הזה ולכתוב פשוט:
var score1 = Source.fromFile(filename).getLines.map(score)
אבל זה פחות כיף.
בחלק השני אני מריץ עוד לולאה הפעם עם match/case בתוך ה for כדי לקבל את רשימת הנקודות:
var score2 = for (line <- Source.fromFile(filename).getLines)
yield line match
case "A X" => score("A Z")
case "A Y" => score("A X")
case "A Z" => score("A Y")
case "B X" => score("B X")
case "B Y" => score("B Y")
case "B Z" => score("B Z")
case "C X" => score("C Y")
case "C Y" => score("C Z")
case "C Z" => score("C X")
println(score2.sum)
## רשמים על השפה בינתיים
כמה דברים שלמדתי בינתיים:
1. בסקאלה אינדנטציה חשובה, כמו בפייתון.
2. מערכת הטיפוסים יכולה לעזור, אבל גם יכולה להיות מרגיזה ולהראות הודעות שגיאה לא ברורות.
3. קהילה קטנה אומרת שאפשר למצוא הרבה פחות תיעוד, וגם דוגמאות של סקאלה 2 לפעמים יהיו שונות מסקאלה 3.
4. ידע ב Java מאוד עוזר להבין את סקאלה.
סך הכל בינתיים אני מחבב את השפה. אמשיך לעדכן. אם יש לכם טיפים איך לכתוב את הקוד בסקאלה יותר נקי אשמח לשמוע בתגובות.1 420
# אבל זה כבר שם...
ואולי בסיס נתונים רלציוני הוא לא כזה רעיון טוב? או לא כזה מתאים לפרויקט שלך?
ואולי Virtual DOM הוא לא כזה רעיון טוב? או לא כזה מתאים לפרויקט שלך?
ואולי ORM לא היה כזה רעיון טוב? או לפחות לא הכי מתאים לפרויקט שלך?
כמה החלטות אנחנו מקבלים רק כי הן שם, כי אין לנו את הידע "להתווכח", וכי זה מה שכולם עושים? כמה פעמים אנחנו מגלים, באיחור של שנה או שנתיים, שאנחנו עובדים הרבה יותר מדי קשה ולא נהנים מהיתרונות של הכלי שבחרנו? ומה עושים במצב כזה?
הבעיה עם תשתיות היא שמאוד קשה להיפטר מהן. בפרויקט ריילס כל הקוד שלי מבוסס על ORM. אם מחר אני רוצה להחליף את זה לגישה פוקנציונאלית לבסיס הנתונים צריך לשכתב הכל. בפרויקט ריאקט כל הקוד מבוסס על Virtual DOM. אם מחר אני רוצה להחליף את זה לגישה ריאקטיבית או אימפרטיבית אצטרך לשכתב את הכל. אלה החלטות שהן כמו דרך חד-סטרית בפיתוח. ואלה גם ההחלטות שאנחנו מקבלים בשלבים הכי מוקדמים של הפרויקט, כשלא יודעים עדיין מספיק על הדרישות, האתגרים והרבה פעמים גם לא מספיק על הטכנולוגיה שבחרנו. אז לוקחים מה שכולם לוקחים ומקווים לטוב.
מיקרו סרביסים מהווים סוג של אלטרנטיבה, כי אני בונה מוצר קטן יותר ואז ב"מוצר" הבא אוכל לבחור תשתית אחרת. אבל הם מכניסים עולם שלם של סיבוכים בהיבט של תקשורת וסינכרון בין הסרביסים.
פיתוח POC הוא ניסיון נוסף להתמודד עם האתגר, אבל בדרך כלל גם הוא לא פותר את הבעיה כי ה POC הוא בסיסי מדי ודווקא במקרים הפשוטים עוד לא רואים את הבעיות של התשתית שבחרנו.
מה בכל זאת אפשר (חוץ מלאכול את הלב כמובן)? זה כבר תלוי בפרויקט. הרבה פעמים אני מגלה שכתיבה מחדש בטכנולוגיה יותר מתאימה, אפילו אחרי חצי שנה פיתוח, כמעט לא לוקחת זמן, כי עיקר ההתעסקות בפיתוח הראשוני היתה בכלל בלזהות את הבעיה. אפשר בהחלט לשבת שבועיים ולשכתב קוד שנכתב בחצי שנה. אופציה שניה אם אפילו את השבועיים האלה קשה למצוא, או אם נזכרתם מאוחר מדי, היא להחליף תשתית לאט ובזהירות. בהתחלה להוסיף מודולים חדשים בתשתית החדשה ואחר כך לאט להחליף דברים קיימים. מה שחשוב הוא להסתכל על המערכת כל הזמן בתור משהו "חי", ולהיזהר מלהיתקע לאורך זמן עם תשתית שרק מעכבת אותנו, רק בגלל שהיא היתה זמינה בתחילת הפרויקט.
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
