ToCode
الذهاب إلى القناة على Telegram
1 420
المشتركون
+124 ساعات
+17 أيام
-430 أيام
أرشيف المشاركات
1 420
# איך לכתוב לבד דיאלוג מודאלי בריאקט ב 2023
בוב דילן שר על הזמנים שמשתנים ואיתם גם היכולות המובנות בדפדפנים. הרבה זמן אחד הדברים שהיה חסר למתכנתים ולמתכנתות והיה אחת הסיבות לשילוב ספריה חיצונית ל UI באפליקציית ריאקט, היה דיאלוג מודאלי. היום עם תמיכה מובנית בדפדפנים זו כבר לא בעיה והאתגר היחיד הוא להתאים את ממשק הפקודות של הדפדפן לאופי הדקלרטיבי של ריאקט. בואו נראה איך זה עובד.
## פיתוח דיאלוג מודאלי בריאקט בגישה אימפרטיבית
האלמנט
dialog של HTML יכול להיפתח בצורה מודאלית בעזרת הפונקציה showModal של JavaScript. לכן הדרך הכי פשוטה להציג דיאלוג מודאלי בריאקט היא ליצור אלמנט כזה, לשמור אותו ב ref ולקרוא לפונקציה כשרוצים להציג או להסתיר אותו. הנה הקוד:
import { useRef } from "react";
export default function App() {
const modal = useRef(null);
const show = () => {
modal.current.showModal();
};
const hide = () => {
modal.current.close();
};
return (
<div className="App">
<p>
Hello World
<button onClick={show}>show dialog</button>
</p>
<dialog ref={modal}>
<p>Dialog Content</p>
<button onClick={hide}>Close</button>
</dialog>{" "}
</div>
);
}
## הפיכת המנגנון לדקלרטיבי
הבעיה שמתכנתי ריאקט לא אוהבים להגדיר מה קורה כשלוחצים על כפתור. אנחנו מעדיפים להסתכל על היישום, לדבר על הסטייט שלו ולחשוב איך הממשק נראה כשמשתני הסטייט מכילים ערכים מסוימים. הקוד למעלה אומנם מציג ומסתיר את הדיאלוג, אבל לא ברור איך לסנכרן את הדיאלוג המודאלי הזה עם חלקים אחרים ביישום.
הדרך המרכזית לחבר בין קוד ריאקטי לקוד דפדפן שאינו ריאקטי היא הפקודה useEffect. אלה הצעדים שניקח בשביל להפוך את הקוד לדקלרטיבי:
1. נוציא את הדיאלוג לקומפוננטה משלו, שתציג בתוך הדיאלוג את הילדים שלה.
2. נשתמש ב Prop של הקומפוננטה כדי להחליט אם צריך להציג או להסתיר את הדיאלוג.
3. נעביר ב Prop פונקציה שתיקרא כשמשתמש סוגר את הדיאלוג.
4. נפעיל את הקומפוננטה מבחוץ ונשמור סטייט שיקבע אם הדיאלוג יוצג או יוסתר.
קוד? ברור:
import { useRef, useState, useEffect } from "react";
function Modal({ children, visible, onClose }) {
const modal = useRef(null);
const show = () => {
modal.current.showModal();
};
const hide = () => {
modal.current.close();
};
useEffect(() => {
if (visible) {
show();
} else {
hide();
}
}, [visible]);
return (
<dialog ref={modal}>
{children}
<button onClick={onClose}>Close</button>
</dialog>
);
}
export default function App() {
const [showModal, setShowModal] = useState(false);
return (
<div className="App">
<p>
Hello World
<button onClick={() => setShowModal(true)}>show dialog</button>
<p>{JSON.stringify(showModal)}</p>
<p>
<label>
<input
type="checkbox"
checked={showModal}
onChange={(e) => setShowModal(e.target.value)}
/>
Show Modal
</label>
</p>
<Modal visible={showModal} onClose={() => setShowModal(false)}>
<p>Hello world</p>
</Modal>
</p>
</div>
);
}
ולייב בקודסנדבוקס למיטיבי לכת:
https://codesandbox.io/s/busy-bassi-rfpnyt?file=/src/App.js:0-11351 420
# איך לכתוב לבד דיאלוג מודאלי בריאקט ב 2023
בוב דילן שר על הזמנים שמשתנים ואיתם גם היכולות המובנות בדפדפנים. הרבה זמן אחד הדברים שהיה חסר למתכנתים ולמתכנתות והיה אחת הסיבות לשילוב ספריה חיצונית ל UI באפליקציית ריאקט, היה דיאלוג מודאלי. היום עם תמיכה מובנית בדפדפנים זו כבר לא בעיה והאתגר היחיד הוא להתאים את ממשק הפקודות של הדפדפן לאופי הדקלרטיבי של ריאקט. בואו נראה איך זה עובד.
## פיתוח דיאלוג מודאלי בריאקט בגישה אימפרטיבית
האלמנט
dialog של HTML יכול להיפתח בצורה מודאלית בעזרת הפונקציה showModal של JavaScript. לכן הדרך הכי פשוטה להציג דיאלוג מודאלי בריאקט היא ליצור אלמנט כזה, לשמור אותו ב ref ולקרוא לפונקציה כשרוצים להציג או להסתיר אותו. הנה הקוד:
import { useRef } from "react";
export default function App() {
const modal = useRef(null);
const show = () => {
modal.current.showModal();
};
const hide = () => {
modal.current.close();
};
return (
<div className="App">
<p>
Hello World
<button onClick={show}>show dialog</button>
</p>
<dialog ref={modal}>
<p>Dialog Content</p>
<button onClick={hide}>Close</button>
</dialog>{" "}
</div>
);
}
## הפיכת המנגנון לדקלרטיבי
הבעיה שמתכנתי ריאקט לא אוהבים להגדיר מה קורה כשלוחצים על כפתור. אנחנו מעדיפים להסתכל על היישום, לדבר על הסטייט שלו ולחשוב איך הממשק נראה כשמשתני הסטייט מכילים ערכים מסוימים. הקוד למעלה אומנם מציג ומסתיר את הדיאלוג, אבל לא ברור איך לסנכרן את הדיאלוג המודאלי הזה עם חלקים אחרים ביישום.
הדרך המרכזית לחבר בין קוד ריאקטי לקוד דפדפן שאינו ריאקטי היא הפקודה useEffect. אלה הצעדים שניקח בשביל להפוך את הקוד לדקלרטיבי:
1. נוציא את הדיאלוג לקומפוננטה משלו, שתציג בתוך הדיאלוג את הילדים שלה.
2. נשתמש ב Prop של הקומפוננטה כדי להחליט אם צריך להציג או להסתיר את הדיאלוג.
3. נעביר ב Prop פונקציה שתיקרא כשמשתמש סוגר את הדיאלוג.
4. נפעיל את הקומפוננטה מבחוץ ונשמור סטייט שיקבע אם הדיאלוג יוצג או יוסתר.
קוד? ברור:
import { useRef, useState, useEffect } from "react";
function Modal({ children, visible, onClose }) {
const modal = useRef(null);
const show = () => {
modal.current.showModal();
};
const hide = () => {
modal.current.close();
};
useEffect(() => {
if (visible) {
show();
} else {
hide();
}
}, [visible]);
return (
<dialog ref={modal}>
{children}
<button onClick={onClose}>Close</button>
</dialog>
);
}
export default function App() {
const [showModal, setShowModal] = useState(false);
return (
<div className="App">
<p>
Hello World
<button onClick={() => setShowModal(true)}>show dialog</button>
<p>{JSON.stringify(showModal)}</p>
<p>
<label>
<input
type="checkbox"
checked={showModal}
onChange={(e) => setShowModal(e.target.value)}
/>
Show Modal
</label>
</p>
<Modal visible={showModal} onClose={() => setShowModal(false)}>
<p>Hello world</p>
</Modal>
</p>
</div>
);
}
ולייב בקודסנדבוקס למיטיבי לכת:
https://codesandbox.io/s/busy-bassi-rfpnyt?file=/src/App.js:0-11351 420
// val inputStream: LazyList[String] = demoInput.linesIterator.to(LazyList)
val inputStream = LazyList.from(Source.fromFile("./input.txt").getLines())
val unpackedInput: LazyList[Dir] = inputStream.flatMap(parseInput)
val headMoves: LazyList[Knot] = unpackedInput.scanLeft[Knot](Knot(0, 0))((knot, dir) => knot.move(dir))
val tailMovesPart1 = headMoves.scanLeft[Knot](Knot(0, 0))((tail, head) => tail.follow(head))
println(tailMovesPart1.toSet.size)
## חלק 2
בחלק השני ביקשו לדמיין חבל עם יותר קשרים - למשל 10 קשרים. הקשר השני עוקב אחרי הראשון, השלישי אחרי השני, הרביעי אחרי השלישי וכך הלאה. השאלה היתה בכמה מיקומים ייחודיים היה הקשר האחרון. בהינתן הקוד שכבר כתבנו מעבר ל 10 קשרים הוא בסך הכל תוספת של לולאה שמייצרת עוד רשימות:
var tailMovesPart2: LazyList[Knot] = headMoves
for (i <- Range(1, 10)) {
tailMovesPart2 = tailMovesPart2.scanLeft[Knot](Knot(0, 0))((tail, head) => tail.follow(head))
}
println(tailMovesPart2.toSet.size)
סך הכל הפיתרון בסקאלה לא יצא שונה משמעותית מפיתרון דומה שהייתי כותב בשפה פונקציונאלית אחרת כמו קלוז'ר או אליקסיר. את עיקר זמן העבודה על התרגיל ביליתי בלחפש את הפונקציות המתאימות ולהבין את התחביר. הגמישות של סקאלה מאפשרת לממש את הפיתרון תוך שילוב של כתיב מונחה עצמים עם כתיב פונקציונאלי.1 420
# פיתרון Advent Of Code 2022 Day 9 בסקאלה
בסקאלה יש אוסף מרשים של פונקציות לעבודה עם רשימות וליצירת רשימות מתוך רשימות אחרות. הלכתי לפתור את יום 9 של AoC האחרון כדי לשחק עם כמה מהן.
## התרגיל - חלק 1
באתגר של יום 9 היה עלינו לעבור גשר חבלים. אני חייב להודות שלא הבנתי עד הסוף את סיפור הרקע אבל בגדול יש חבל שיש לו שני קשרים, קשר בכל קצה. אנחנו מקבלים את המסלול שעובר קשר אחד (ה head) וצריך להבין ממנו מה המסלול שיעשה הקשר השני. בגדול הקשר השני צריך "לעקוב" אחרי הקשר הראשון, כך שכל פעם שהראשון נמצא במרחק 2 משבצות מהשני הקשר השני יזוז משבצת אחת לכיוון הראשון. יש בדף התרגיל כמה איורים שממחישים את התנועה הזאת ואם אתם לא מכירים את התרגיל אני ממליץ להציץ שם.
הקלט יהיה המסלול שעובר הקשר הראשון, כאשר שני הקשרים נעים על מישור דו-מימדי ויכולים לזוז למעלה, למטה, ימינה ושמאלה. מסלול נראה כך:
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
וזה אומר שהקשר הראשון יזוז 4 צעדים ימינה, אחרי זה 4 צעדים למעלה, שלושה צעדים שמאלה ואז עוד צעד למטה, ארבעה צעדים ימינה, צעד למטה, חמישה שמאלה ושניים ימינה. הקשר השני עוקב לפי הלוגיקה שתיארתי קודם ומוסברת הרבה יותר טוב בדף האתגר.
## מימוש הפונקציות של הקשרים בסקאלה
הצעד הראשון בפיתרון היה להבין את ה"קשרים" והפעולות שלהם - זיכרו שהקשר הראשון זז לפי ההוראות והקשר השני "עוקב" אחרי הראשון, לכן לקשר יש מיקום (קואורדינטות x ו y), ושתי פונקציות, תזוזה לפי הוראה ותזוזה אחרי קשר אחר. המימוש בסקאלה של שתי הפונקציות כולל לוגיקת התזוזה אחרי קשר אחר הוא:
enum Dir:
case U, D, L, R
case class Knot(x: Int, y: Int) {
def move(direction: Dir): Knot =
direction match
case Dir.R => Knot(this.x + 1, this.y)
case Dir.L => Knot(this.x - 1, this.y)
case Dir.U => Knot(this.x, this.y - 1)
case Dir.D => Knot(this.x, this.y + 1)
def follow(head: Knot): Knot =
// head touches tail
if (((head.x - this.x).abs <= 1) && ((head.y - this.y).abs <= 1)) {
return this
}
var nextPosition = this;
if (nextPosition.x < head.x) {
nextPosition = nextPosition.move(Dir.R)
} else if (nextPosition.x > head.x) {
nextPosition = nextPosition.move(Dir.L)
}
if (nextPosition.y < head.y) {
nextPosition = nextPosition.move(Dir.D)
} else if (nextPosition.y > head.y) {
nextPosition = nextPosition.move(Dir.U)
}
nextPosition
}
אני בטוח שאפשר לכתוב את הפונקציה follow יותר יפה ואתם מוזמנים לשלוח בתגובות מימושים שלכם, אבל אפילו המימוש המסורבל שלי היה מספיק טוב פה.
## פיתרון חלק 1 עם רשימות
ואחרי כל ההקדמה אפשר לגשת לכתוב את הקוד המעניין - הרשימות והטרנספורמציות עליהן. אני מתחיל עם הקלט שזו רשימת צעדים בסגנון של:
R 4
U 2
בגלל שהקשרים זזים רק צעד אחד כל פעם, אני מעדיף "לפתוח" את הקידוד ולייצר רשימה של פקודות תנועה, כלומר להפוך את הקלט לרשימה כמו:
R R R R U U
בסקאלה זה יהיה עם הקוד הבא:
def parseInput(input: String): LazyList[Dir] = {
val Array(name, countStr) = input.split(" ")
val count = countStr.toInt
LazyList.fill(count)(Dir.valueOf(name))
}
val inputStream = LazyList.from(Source.fromFile("./input.txt").getLines())
val unpackedInput: LazyList[Dir] = inputStream.flatMap(parseInput)
עכשיו אפשר ליצור רשימה של כל המיקומים של הקשר הראשון. הפונקציה scanLeft של רשימה עצלה בסקאלה מקבלת ערך התחלה - שזה יהיה הקשר בנקודה (0, 0), ופונקציה שצריכה לקבל את הערך הנוכחי ואלמנט מהרשימה ולהחזיר את האלמנט הבא ברשימת התוצאות. לכן חישוב כל המיקומים של קשר ה head יהיה:
val headMoves: LazyList[Knot] = unpackedInput.scanLeft[Knot](Knot(0, 0))((knot, dir) => knot.move(dir))
בשביל לעקוב אחרי head אני משתמש שוב ב scan אבל הפעם רשימת הקלט היא headMoves והפונקציה תקרא ל follow:
val tailMovesPart1 = headMoves.scanLeft[Knot](Knot(0, 0))((tail, head) => tail.follow(head))
לבסוף ניקח את כל רשימת ה tailMovesPart1 לתוך Set ונדפיס את הגודל שלה, כדי לקבל את מספר הערכים השונים ברשימה. סך הכל הפיתרון של החלק הראשון הוא:
@main
def main(): Unit = {1 420
# הסרטים של טום הנקס
אחת הדוגמאות בקורס SQL החדש של הארוורד היא השאילתה הבאה שמושכת מבסיס הנתונים את כל הסרטים בהם שיחק טום הנקס:
SELECT "title" FROM "movies"
WHERE "id" IN (
SELECT "movie_id" FROM "stars"
WHERE "person_id" = (
SELECT "id" FROM "people"
WHERE "name" = 'Tom Hanks'
)
);
אפשר להבין את השאילתה, אבל לא ברור למה, כששאילתה מקבילה בבסיס נתונים גרפי היא:
MATCH (m:Movie)-[:STAR]-(p:Person{name: 'Tom Hanks'})
RETURN m.title;
יותר מהשאילתות, הסיפור פה הוא על מבנה מודל המידע. כשהמידע בנוי טוב, כלומר מתאים ליישום ולפיצ'רים שאנחנו רוצים לבנות, השאילתות פשוטות ואנחנו יכולים להתקדם מהר יותר בפיתוח. אין ספק שחשוב להבין מה זה אינדקס ואיך אינדקסים יכולים לשפר את הביצועים בשאילתות SQL מסובכות, אבל יותר חשוב להבין את המידע של הישום והאם מבנה הטבלאות שבחרנו (או בכלל סוג בסיס הנתונים) מתאים למידע וליישום שלנו.1 420
TCategory(this.out("belongs_to").hasLabel("category"))
}
case class TCategory[S](value: GraphTraversal[S, Vertex]) extends CustomTraversal[S, Vertex] {
def posts(): TPost[S]=
TPost(this.in("belongs_to").hasLabel("post"))
}
וכך קודדתי את היחס בין פוסט לקטגוריה באמצעות מתודה של פוסט - בשביל לקבל את הקטגוריה של הפוסט ממשיכים לאורך הקשת belongs_to.
אחרי מימוש התשתית אני יכול לכתוב שאילתות כמו:
g.Post("first").categories.toList
כדי לקבל את כל הקטגוריות של הפוסט first, או אפילו:
g.Post("first").categories.posts.toList
כדי לקבל את כל הפוסטים ששייכים לאותן קטגוריות כמו הפוסט first.1 420
# הצעה ל DSL לשאילתות על גרף בסקאלה וגרמלין
לפני כמה שבועות פרסמתי כאן מדריך לעבודה עם בסיסי נתונים גרפיים באמצעות גרמלין וסקאלה. מאז המשכתי לשחק עם הטכנולוגיות והתחלתי לבנות שיטת עבודה לניסוח שאילתות לשימוש חוזר. אני מפרסם כאן בתור טיוטה, אם יש פה אנשים שמכירים סקאלה או גרמלין יותר טוב ממני ורוצים לשתף למה זה רעיון טוב (או גרוע) אשמח לשמוע בתגובות.
## הבעיה עם גרמלין
באותו מדריך מלפני כמה שבועות הראיתי קטעי קוד שנראו כך:
g.addV("post")
.property("slug", slug)
.property("title", title)
.property("publishedAt", LocalDateTime.parse(publishedAt, formatter))
.next()
המשתנה g מייצג חיבור לגרף ופונקציות כמו addV מבצעות פעולות על הגרף (פונקציית addV מוסיפה צומת). בשביל לגשת לאותו פוסט שיצרתי אני כותב שאילתה שזה משהו מסוג "חיפוש בגרף":
g.V().has("post", "slug", "first")
הקוד הזה עדיין לא מחזיר צומת אלא רק מייצג "חיפוש בגרף". המתודה next שלו תחזיר את הצומת הראשון שמתאים לחיפוש, והמתודה toList תחזיר את כל הצמתים שמתאימים לחיפוש.
הבעיה עם כל החיפושים האלה היא שכתיבת שאילתות דורשת היכרות עם מבנה הגרף. אם בנוסף לפוסטים יש לי גם קטגוריות וכל פוסט מחובר לקטגוריה עם קשת belongs_to אז שאילתה כזו תחזיר את כל הקטגוריות של פוסט מסוים:
g
.V()
.has("post", "slug", "first")
.out("belongs_to")
ושאילתה כזו תחזיר את כל הפוסטים ששייכים לאותה קטגוריה כמו first:
g
.V()
.has("post", "slug", "first")
.out("belongs_to")
.in("belongs_to")
וכאן אנחנו כבר מתחילים לראות את הבעיה - ככל שהיישום גדל אני צריך לכתוב את היחסים בין הצמתים והמאפיינים שלהם בשאילתות שונות המפוזרות ביישום. גם אם אני יכול לכתוב את השמות בתור קבועים, שינוי של היחסים, לדוגמה של כיוון הקשת או הכנסת צומת נוסף בין פוסט לקטגוריה, יגרום לשינוי רוחבי ביישום ועוד כזה שמערכת הטיפוסים של סקאלה לא תצליח לזהות.
## פיתוח שאילתות לשימוש חוזר באמצעות עטיפת מסלולים בגרף
אבל אם נעצור להתבונן שוב בשאילתות נשים לב לתבנית מעניינת - כל מתודה מחזירה אוביקט מסוג "חיפוש בגרף", וכל חיפוש בגרף הוא משהו שאפשר להמשיך אותו עם חיפושים נוספים. בעצם כל המתודות של גרמלין הן חיפושים בגרף השמורים לשימוש חוזר.
בשביל להתאים את גרמלין ליישום שלי, כל מה שאני צריך זה להוסיף קלאסים שמתאימים לסוגי הצמתים שלי ביישום, ולחבר את הקלאסים האלה בתור מתודות חדשות של מחלקת החיפוש בגרף. בואו נראה את זה בקוד.
המאפיין הראשון שאני רוצה להגדיר נקרא CustomTraversal והוא מייצג חיפוש בגרף שמחזיר משהו מטיפוס של היישום. כלומר כן זה צומת, אבל צומת שמייצג משהו, למשל צומת שמייצג פוסט או צומת שמייצג קטגוריה:
trait CustomTraversal[S, E] {
val value: GraphTraversal[S, E]
}
בהינתן אוביקט מסוג זה, אני רוצה להמיר אותו בצורה אוטומטית לשדה ה value שלו, כדי שאפשר יהיה להשתמש בו כאילו היה לנו ביד GraphTraversal ולכן אני מגדיר המרה אוטומטית:
implicit def toValue[S, E](t: CustomTraversal[S, E]): GraphTraversal[S, E] = t.valueאחרי שבנינו אותו אפשר להשתמש בו כדי טיפוסים לחיפושים, למשל ורטקס שמייצג פוסט יהיה:
case class TPost[S](value: GraphTraversal[S, Vertex]) extends CustomTraversal[S, Vertex]
או ורטקס שמייצג קטגוריה:
case class TCategory[S](value: GraphTraversal[S, Vertex]) extends CustomTraversal[S, Vertex]
ואז אפשר להרחיב את הגרף כדי שיוכל להחזיר פוסט או קטגוריה:
extension (g: GraphTraversalSource)
def Post(slug: String): TPost[Vertex] =
TPost(value = g.V().has("post", "slug", slug))
def Category(name: String): TCategory[Vertex]=
TCategory(value = g.V().has("category", "name", name))
וזה כבר עובד, כלומר אני יכול לכתוב:
g.Post("first").next()
כדי לקבל את הורטקס שמתאים לפוסט שנקרא first. אבל זה לא הסוף. נזכור שהאתגר היה לקודד בצורה בטוחה ומתאימה לשימוש חוזר את היחס בין פוסטים לקטגוריות, לכן אני מרחיב את הקלאסים המותאמים שכתבתי קודם:
case class TPost[S](value: GraphTraversal[S, Vertex]) extends CustomTraversal[S, Vertex] {
def categories(): TCategory[S]=1 420
# ואיך בכל זאת אפשר לקצר זמנים?
דיון מעניין ברדיט התחיל בפוסט הזה עם הכותרת "לנסות לדבר עם מתכנתים כדי לקצר זמני פיתוח זה כמו לדבר עם החזאי כדי לשפר את מזג האוויר". אני מבין את האנגלוגיה ויש בה הרבה אמת. אבל זה לא סוף הסיפור.
כמו החזאי, גם אנחנו (לפעמים) יודעים להעריך כמה זמן ייקח לכתוב קוד למימוש מנגנון מסוים. וכמו החזאי גם לנו אין הרבה מה לעשות בנוגע לזמן שזה ייקח. הייתי שמח לחשוב או לכתוב קוד מהר יותר, אבל מניסיון עבר זה הזמן שלוקח לי לחשוב ולפתור בעיות מהסוג הזה. מנהלים שינסו לשכנע אותי לפתור את הבעיה יותר מהר יכולים להיות הכי משכנעים בעולם, אבל לרוב המתכנתים שאני מכיר לא חסר שכנוע ולא חסרה מוטיבציה.
בשביל לקצר זמני פיתוח כדאי להתמודד עם האתגרים האמיתיים של המפתחים. לרוב אלה יהיו:
1. עבודה על בעיות חדשות בלי מספיק ידע על הבעיה או על הכלים. אלה מתכנתי ה PHP שעכשיו צריכים לכתוב אפליקציית Front End בריאקט. כשהכל חדש ההתקדמות איטית יותר והרבה פעמים בכיוון הלא נכון.
2. עבודה על מוצר שהאיפיון שלא לא מספיק טוב, כך שכל גירסה שוברת הנחות יסוד מהותיות מגירסאות קודמות.
3. עבודה על פיצ'רים גדולים מדי עבור המוצר במצבו הנוכחי (וכן ברור לי שמנהלי המוצר שמאפיינים את הפיצ'רים לא מרגישים שאלה פיצ'רים גדולים מדי).
4. הבדלי רמות משמעותיים בין המתכנתים בצוות, שיוצרים עומס על חלק מהצוות ותופעה של "צוואר בקבוק".
במקום לבזבז זמן על וויכוחים מנהלים טובים צריכים להבין את הצוות ואת החסמים האמיתיים שמאטים את העבודה ולהשקיע את הזמן בהסרת או צמצום אותם חסמים.
1 420
# לימוד לא לינארי
לימוד לינארי זה מה שקורה בכיתה - גם ברמת השיעור וגם ברמת השנה. לימוד לינארי זה לשבת ולהקשיב למסלול שמישהו אחר בנה בשבילך, לפתור בכל שלב את התרגילים שילמדו אותך את המיומנות של אותו שלב ומתישהו להצליח לחבר את כל הנקודות כדי לבנות מוצר.
בלימוד לינארי לא צריך לחשוב מה הדבר הבא שצריך לעשות. מספיק להיכנס לכיתה או ללחוץ Play על הוידאו הבא. גם אין פערי ידע (לפחות בתיאוריה). אם סיימת שלב קיבלת גם את כל המיומנות של אותו השלב דרך הקשבה לשיעור ופיתרון תרגילים. ואפילו אם לא מבינים משהו, כל מה שצריך לעשות זה לדפדף אחורה בספר או לצפות בסרטי וידאו קודמים שוב כדי לחזק את היסודות ואז אפשר יהיה להמשיך.
גם מורים שבונים שיעורים בצורה לינארית יגלו שהחיים שלהם לא מאוד מסובכים. נכון צריך לשבור את הראש ולבחור מה ללמד ובאיזה סדר, אבל אחרי מספיק פעמים שתלמדו משהו אתם תגלו שאפשר לעשות אופטימיזציה לחווית הלמידה ואפשר לבנות מסלול לינארי שידבר לאנשים.
אז איפה בכל זאת הבעיה? שני אתגרים-
1. הראשון הוא שאנחנו לא לומדים כך. מאוד קשה לבני אדם ללמוד משהו באמצעות "בניה מלמטה" של שכבות כמו שבונים בניין. הלימוד האנושי הוא ספיראלי, וכל פעם שאנחנו מגלים פיסות מידע חדשות אנחנו מחברים את הידע החדש לדברים שאנחנו יודעים, ודרך הידע החדש מגלים דברים חדשים על השכבות שכביכול למדנו. פה באתר אני פוגש לא מעט אנשים שמסיימים קורס וידאו ואחרי כמה חודשים של עבודה וניסיון חוזרים לצפות בו פעם נוספת ומגלים דברים חדשים לגמרי.
2. והאתגר השני הוא שהמציאות לא בנויה בצורה לינארית. יש פשוט יותר מדי חומר, יותר מדי דברים ללמוד וכל ניסיון ללמוד את הכל מההתחלה בצורה מסודרת חייב לדלג על נושאים חשובים רק בשביל להגיע לאיזושהי מיומנות בעלת ערך.
אם אתם רוצים לשפר את אפקטיביות הלמידה שלכם, נסו להחליף את הלימוד הלינארי בלימוד ספיראלי, כלומר התחילו בלימוד מהיר ממש דרך דפדוף בכמה דוגמאות, לאחר מכן בנו כמה דברים קטנים (ממה שאתם מצליחים) עם מה שלמדתם ואז חזרו לסיבוב נוסף בו תתעמקו באותן דוגמאות עליהן עברתם בחטף ואת התיאוריה מאחוריהן. בעזרת הידע החדש תוכלו לשפר את הקוד שכתבתם וכך המשיכו בסבבים, כולל הרחבה של הלופ מדי פעם כדי להכיר דוגמאות חדשות וטכניקות חדשות לשימוש באותה טכנולוגיה.
1 420
# הפוסט שלא כתבתי
הפוסט שלא כתבתי היה אמור לספר על בעיה ממש מתוחכמת. כל כך מסובכת שבזמן אמת אפילו לא הבנתי אותה עד הסוף. וכמובן על הפיתרון הסופר מתוחכם שלה, שבהתחלה לא עבד ורק אחרי כמה ניסיונות הצלחתי להגיע לנוסחה שבדיוק פתרה את הבעיה.
הפוסט שלא כתבתי היה אמור לספר לכם על הגורם האמיתי לבעיה הזאת, ואת כל הדרך שעשיתי כדי לגלות אותה. וגם להראות את כל הפיתרונות שלא עבדו ולהסביר מה היו הבעיות שלהם, ואיך הפיתרון שעבד (אפילו שהוא מאוד מסובך) קלע כמו חץ למטרה ובדיוק פתר את הבאג.
הבעיה עם הפוסט שלא כתבתי היתה שתוך כדי חיפוש כל ההסברים האלה פתאום הבנתי שהבעיה היתה הרבה יותר פשוטה ממה שחשבתי בהתחלה. כל כך פשוטה שאין אפילו מה לכתוב עליה. והפיתרון הסופר מסובך שלקח לי שעות לכתוב? הוא התגלה כ Workaround טפשי שאפילו לא מטפל בכל המקרים. המחקר לקראת הפוסט שלא כתבתי מוטט את כל הקוד המסובך כמו מגדל קלפים, והשאיר אותי ללא פוסט אבל עם קוד פשוט שעובד הרבה יותר טוב.
הפוסט שלא כתבתי מזכיר לי שהכתיבה על קוד הכרחית בדיוק כמו כתיבת הקוד.
1 420
# לא Java, מחרוזת וביטוי רגולארי זה שני דברים שונים
ל Java יש API לעבודה עם ביטויים רגולאריים דרך החבילה java.util.Pattern. עם החבילה הזאת אפשר לכתוב דברים כמו:
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
או אפילו להשתמש בפונקציות הסטטיות ולכתוב:
boolean b = Pattern.matches("a*b", "aaaaab");
וביום רגיל היינו עוצרים פה את השיחה על ביטויים רגולאריים וחוזרים לדבר על קוד. אבל עם Java שום יום אינו יום רגיל.
מחוץ לחבילת הביטויים הרגולאריים יש חבילה של מחרוזות ובה הפונקציה split:
public String[] split(String regex)
וכן שם הפרמטר נבחר בכוונה - ג'אווה מתיחס למחרוזת ההפרדה ל split כמו ביטוי רגולארי. קוד? בטח. הנה שתי דוגמאות:
import java.util.Arrays;
class Main {
public static void main(String[] args) {
// prints: [hello, world]
System.out.println(Arrays.toString("hello-world".split("-")));
// prints: []
System.out.println(Arrays.toString("hello.world".split(".")));
}
}
השורה הראשונה מדפיסה מערך עם שני איברים, הראשון הוא המילה hello והשני המילה world. השורה השניה מחזירה מערך ריק, כי נקודה היא ביטוי רגולארי שמתאים לכל תו, ולכן כל תו הוא תו הפרדה ולכן לא נשארו דברים אמיתיים (מופרדים) לשים במערך.
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
