הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

הלאה

27/03/2021

ישבתי היום עם הבת שלי על משחק אותיות פשוט: לקחנו 5 כרטיסיות, כתבנו עליהם את האותיות "ג", "ד", "ו", "ל" ו-"ה" ולימדתי אותה איך להרכיב את המילה "גדולה" (כי היא כבר ילדה גדולה שיודעת אותיות). זה לקח כמה ניסיונות אבל בסוף היא הצליחה ממש טוב להרכיב את המילה, לא משנה כמה ערבבתי את הכרטיסיות ואפילו כשהוספתי כרטיסיות עם אותיות לא קשורות למילה זה לא הפריע לה.

היא הסכימה לכל טריק שרציתי להוסיף למשחק מלבד אחד - רק לא להחליף מילה.

מרגע שהיא קלטה שהיא יודעת להרכיב את המילה "גדולה" היא לא היתה מוכנה לוותר על התחושה של לעשות משהו כמו שצריך. המחשבה על החזרה אחורה לטעויות, לבלבול, לחוסר וודאות פשוט הוציאה לה את החשק ללמוד.

אותו פחד מהחזרה לבלבול, לטעויות ולחוסר הוודאות לא נעלם עם הגיל ולא משתנה כשעוברים ללמוד דברים יותר קשים מאותיות ומילים. הוא שם כשאני מתלבט אם זה זמן טוב עכשיו להיכנס לעולם ה Front End, או אם שווה ללמוד איך עובדים חוזרים חכמים ויודע שכבר מהצעד הראשון בתחום החדש ארגיש את אותו תסכול, אותו בלבול ואותו חוסר וודאות.

הלאה.

למילה הבאה. לטכנולוגיה הבאה. לתחום הבא. כמו שלמדנו את הדבר שאנחנו עכשיו טובים בו, נצליח ללמוד גם את הדבר הבא. ואחרי? נמשיך למילה הבאה כמובן.

להוריד רעיונות מהשולחן

26/03/2021

אחד הדברים שאנחנו יודעים על תכנות זה שלא תמיד הרעיון הראשון שנגיע אליו יהיה הכי טוב האפשרי, ויותר גרוע, לא תמיד הרעיון הראשון שנגיע אליו יהיה בכלל ישים. זאת אחת הסיבות שלפעמים מביך לצפות ב Live Coding - אתה מסתכל על בן אדם מנסה להתקדם בכיוון שלא יכול לעבוד בגלל מגבלה שהוא עדיין לא הבין אותה, ואתה יודע שעוד רגע הוא הולך להתנגש בקיר וזה הולך לכאוב.

אבל מחוץ ל Live Coding, בעולם האמיתי, החוויה של להתקדם עם רעיון עד שמתנגשים בקיר היא חלק בלתי נפרד מתהליך הלימוד במיוחד של טכנולוגיות חדשות. יש לי איזה כיוון לגבי בחירה טכנולוגית, אני כותב סביבו את הקוד, מכניס למערכת נתונים אמיתיים ורואה איך היא מתרסקת. חלק מהפעמים היא תתרסק בגלל ששכחתי משהו קטן ואני מיד יכול לתקן ולהתקדם, ופעמים אחרות היא תתרסק כי חשבתי שהמידע יעבור ברשת מספיק מהר וראיתי שבעולם האמיתי המערכת עובדת הרבה יותר לאט מאשר בתוכנית שלי.

"לא, אתה לא יכול לכתוב סקריפט שמושך את הנתונים מהאתר ההוא כי יש שם מנגנוני הגנה חזקים" זה לקח טוב שעוזר לנו בפעם הבאה להתחיל מבדיקת מנגנוני ההגנה לפני שרצים לכתוב את הסקריפט. וברגע שהורדתי את הרעיון הזה מהשולחן אני פותח את הדלת לרעיונות חדשים שאולי כן יעבדו טוב יותר.

גישה גרפית לשרת יוניקס מרוחק

25/03/2021

רוב הפעמים שנצטרך לעשות עבודה על שרת מרוחק נעדיף להשתמש בממשק טקסטואלי כי הוא מהיר ויציב יותר בעבודה ברשת. אבל מדי פעם כן נצטרך לעבוד על תוכנית גרפית או אפילו על שולחן עבודה שלם שרצים על שרת מרוחק. בפוסט זה אציג את שתי הדרכים המרכזיות לעשות את זה.

המשך קריאה

העלאת קונטיינר דוקר לענן של AWS

24/03/2021

אתמול פירסמתי מדריך על העלאת קונטיינר דוקר לענן של Microsoft, וכבר כמה אנשים שאלו איך אפשר לפרסם את אותו קונטיינר בענן הגדול יותר של AWS. אז פתחתי את ה AWS CLI וציפיתי להתפנק עם כל הכלים המושקעים של אמזון. לצערי זאת לא היתה החוויה שקיבלתי אבל בכל זאת הצלחתי להגיע לענן וגם לשרוד כדי לספר.

גירסאות עדכניות של docker כוללות מנגנון שנקרא Context. הקונטקסט מספר לדוקר איפה המכונות שצריכות להפעיל את הקונטיינרים יושבות ובאיזה מנגנון אנחנו מדברים עם מכונות אלה (מי ה Orchestrator). לדוקר יש אינטגרציה מובנית עם AWS ועם Azure Devops כך שכל מה שצריך בשביל להעלות קונטיינרים לענן הוא:

  1. ליצור Context ולחבר אותו ל Azure Devops או ל AWS.

  2. להשתמש בפקודות Docker רגילות כדי להפעיל קונטיינרים.

  3. באופן אוטומטי הקונטיינרים ישתמשו בקונטקסט שהגדרנו ויעלו בענן במקום אצלי על המכונה.

ניסוי? בשמחה. אבל קודם נצטרך לעבור את מדיניות האבטחה הדרקונית של אמזון. בתוך קונסולת הניהול יש להיכנס לשירות שנקרא IAM וליצור משתמש חדש. יש במדריך כאן רשימה של כל ההרשאות שאותו משתמש צריך. אני לא מצאתי איך לעשות copy/paste בקלות להרשאות אז בחרתי מרשימת ה Policies מה שנראה לי קשור. סך הכל בשביל משתמש שמצליח להעלות קונטיינרים לענן שלהם הוספתי את ה Policies הבאים:

AmazonEC2FullAccess
IAMFullAccess
AWSAgentlessDiscoveryService
ElasticLoadBalancingFullAccess
AmazonEC2ContainerRegistryFullAccess
AWSCloudMapFullAccess
CloudWatchLogsFullAccess
AmazonECS_FullAccess
AmazonRoute53FullAccess
AWSCloudFormationFullAccess

השיטה של להוסיף הרשאות עד שזה עובד מתאימה לפוסט, אבל לאפליקציה אמיתית כן שווה להתאמץ ולמצוא את ההרשאות המינימליות שאתם צריכים.

אחרי יצירת המשתמש וההרשאות אתם צריכים לחבר אותו לדוקר קומפוז שלכם ובשביל זה נכנסים (עדיין ב IAM) לטאב Security Credentials, בוחרים Create Access Key ומשאירים את החלון עם ה Access Key ID וה Secret פתוח.

משם פותחים בחלון חדש מסוף שורת פקודה ומפעילים משם:

docker context create ecs myawscontext

המילה האחרונה בשורה היא שם ה Context ואפשר לשנות אותה לכל שם שאתם רוצים. בתגובה דוקר ישאל אתכם איך להתחבר ל AWS ואתם תוכלו להגיד לו שיש לכם אסימון גישה ותדביקו את המזהה והסוד במסוף.

בסיום התהליך יהיה לכם Context חדש עבור AWS ואפשר לוודא שזה עבד עם:

$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
myawscontext *      ecs                 (eu-north-1)

שימו לב לשורה השניה מסוג ecs ועם האזור שבחרתם ליצור בו את המכונות.

ההבדל השני בין AWS ל Azure הוא שבעבודה עם AWS חייבים להשתמש ב Docker Compose ולא ניתן להעלות קונטיינר בודד. זה לא סוף העולם כי תמיד אפשר ליצור קובץ docker-compose.yml שמתאים לקונטיינר בודד. הנה הדמו שאני העליתי:

version: "3.9"
services:
  web:
    image: "nginxdemos/hello"
    ports:
      - "80:80"

את התוכן שמרתי בקובץ בשם docker-compose.yml בתיקיה חדשה (זה הקובץ היחיד בתיקיה) והמשכתי להעלות אותו לאמזון עם הפקודה:

$ docker compose up

הפקודה נכשלה באקראי מדי פעם על Timeout-ים, אבל גם לפעמים הצליחה, אז אם אתם מפעילים ומקבלים שגיאה שווה להפעיל פעם שניה בשביל לוודא שזו שגיאה אמיתית. כשהכל עובד תוכלו לראות את התוצאה עם:

$ docker compose ps
NAME                                                 SERVICE             STATUS              PORTS
task/mydockerdemo/cdf554598aab4f1fb3f1685bc074de98   web                 Running             mydoc-LoadB-13SBJMOZQ5M30-224910942.us-east-1.elb.amazonaws.com:80->80/http

שכבר נותן לנו את כתובת המכונה שמריצה את הדמו. גלישה לנתיב שמופיע שם תציג דף פתיחה משרת nginx.

אחרי שהכל עבד אפשר להשתמש ב:

$ docker compose down

כדי להוריד את הסרביסים שיצרתם, או להפעיל מחדש docker compose up כדי להעלות גירסה חדשה.

ממש שמחתי לראות את העבודה הטובה שעשו ב Docker כדי לייצר ממשק זהה להעלאת קונטיינרים למספר ספקי ענן. למרות זאת קשה להתעלם מהעובדה שהגישה לשרתים של Azure היתה קלה ומהירה יותר. גם אם נתעלם מנושא בחירת ההרשאות - ב Azure היה לי הרבה יותר קל למצוא את ה Instances שנוצרו בממשק הבקרה שלהם ולראות סטטוס של המכונות דרך ממשק ה Web. זמני המתנה לפקודות docker היו קצרים יותר ולא נתקלתי בכישלונות אקראיים שהסתדרו מעצמם. אם היתה תחרות בין עננים אז לפחות בצד של ממשק עם דוקר הענן של מייקרוסופט מנצח בגדול.

העלאת קונטיינר דוקר לענן של Microsoft

23/03/2021

לפני כמה ימים כתבתי כאן על החשבון החינמי לשנה שפתחתי על Azure Devops, והיום היתה לי הזדמנות לשחק עם עוד מנגנון נחמד של המערכת והוא התקנת קונטיינרים בענן וגיליתי שדוקר כבר עשו בשבילנו את כל העבודה הקשה.

גירסאות עדכניות של docker כוללות מנגנון שנקרא Context. הקונטקסט מספר לדוקר איפה המכונות שצריכות להפעיל את הקונטיינרים יושבות ובאיזה מנגנון אנחנו מדברים עם מכונות אלה (מי ה Orchestrator). לדוקר יש אינטגרציה מובנית עם AWS ועם Azure Devops כך שכל מה שצריך בשביל להעלות קונטיינרים לענן הוא:

  1. ליצור Context ולחבר אותו ל Azure Devops או ל AWS.

  2. להשתמש בפקודות Docker רגילות כדי להפעיל קונטיינרים.

  3. באופן אוטומטי הקונטיינרים ישתמשו בקונטקסט שהגדרנו ויעלו בענן במקום אצלי על המכונה.

ניסוי? בשמחה. בהנחה שיש גם לכם חשבון ב Azure Devops תוכלו להפעיל:

docker login azure

ואז דוקר יפתח דפדפן ויבקש מכם להתחבר לחשבון ה Azure Devops שלכם.

אחרי הכניסה מפעילים את הפקודה:

docker context create aci mycontext

כדי ליצור את הקונטקסט שיהיה כבר מחובר לחשבון ה Azure שלנו. אפשר לראות שהפקודה הצליחה עם:

$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
mycontext *         aci                 yo@centralus

אפשר לראות שיש לי שני קונטקסטים על המכונה: הראשון נקרא default והשני נקרא mycontext.

הפקודה הבאה אומרת לדוקר להתחיל להשתמש בקונטקסט השני:

$ docker context use mycontext

ועכשיו אנחנו מוכנים להרים קונטיינר לענן. הפקודה הבאה יצרה קונטיינר דמו של שרת node.js:

$ docker run -d -p 80:80 nginxdemos/hello

לפי האימג' nginxdemos/hello. כמובן שאתם יכולים לבחור כל Docker Image שתרצו ויש גם תמיכה מלאה ב docker-compose (עליה עוד נדבר בפוסט המשך בעתיד). לפקודת ה run יכול לקחת קצת זמן לסיים לרוץ אבל בסוף זה נגמר ואפשר לראות עם docker ps מה קיבלנו:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS
boring-aryabhata    nginxdemos/hello                        Running             52.182.228.142:80->80/tcp

כניסה לכתובת ה IP של המכונה תציג את דף הכניסה מהאימג', שהוא דף Hello World של שרת nginx.

מה צריך לקרות בשביל שתשנה את דעתך?

22/03/2021

ויכוחים בין מתכנתים נשמעים לכם אולי כמו דבר קטן אבל הרבה פעמים הרוחות יכולות להתלהט בגלל נושאים שלרוב האנשים בכלל לא אכפת מהם: צריך או לא צריך לכתוב בדיקות; כמה שורות כדאי לכתוב בקובץ; צריך או לא צריך עוד קומפוננטה; וים או אימקס.

כשאנחנו תקועים בוויכוח כזה עם חבר ולא לגמרי בטוחים איך הגענו אליו (או איך יוצאים ממנו) השאלה שבכותרת יכולה לעזור: "מה צריך לקרות בשביל שתשנה את דעתך?". האם יש משהו שיכול לקרות שאחריו גם אתה תרגיש שבדיקות שוות את ההשקעה שלנו? האם יש משהו שיקרה בעתיד שיגרום לך לחשוב שהקוד הזה צריך לשבת בקומפוננטה נפרדת? מה צריך בשביל שתבין שאת החלק הזה בקוד צריך לכתוב ב C ?

"מה צריך לקרות בשביל שתשנה את דעתך" לוקחת אותנו לצעד הבא. או שיש כזה דבר ואז אפשר לחשוב איך להגיע אליו בצורה לא מסוכנת, או שאין כזה ואז אפשר להפסיק את הוויכוח ולחפש את הדרכים שכל אחד יוכל להתקדם עם הדברים שחשובים לו בלי לשבור את הקוד לאחר.

הקמת אפליקציית Node.JS בענן של Azure DevOps

21/03/2021

מייקרוסופט הפכה בשנים האחרונות לאחת מספקיות הענן הגדולות, יחד עם AWS וגוגל, וכמו כולם גם הם מחלקים שנה ראשונה מתנה למי שיירשם. החלטתי לקחת את ההצעה ולראות דרך כמה ניסויים איך מתנהג ה Azure DevOps בהשוואה למתחרים.

המשך קריאה

גנרטור רקורסיבי ב Python

20/03/2021

שני פיצ'רים של פייתון שמסתדרים יופי יחד הם ה Generators והרקורסיות. בתרגיל של היום רציתי לשלב את שניהם כדי לחפש מידע במבנה נתונים של עץ. במבנה של עץ יש לכל צומת ערך ואוסף של ילדים אליהם אפשר להגיע מאותו צומת. ב Python אפשר לתאר את כל המבנה בצורה רקורסיבית עם המחלקה הבאה:

@dataclass
class Node:
    value: int
    children: List['Node']

יש המון מבנים מהעולם האמיתי שאפשר לתאר באמצעות מבנה נתונים של עץ - לדוגמה בואו נחשוב על מתקנים בלונה פארק. הערך המספרי ייצג כמה המתקן מדליק והחיבורים יהיו השבילים בין המתקנים, כך שאם מתקן מסוים הוא ברשימת הילדים של מתקן אחר זה אומר שאפשר להגיע מהמתקן שלנו למתקן האחר.

הקוד הבא יכול לייצר את המפה של הלונה פארק במבנה העץ שאנחנו צריכים:


root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])

עכשיו אפשר להמשיך ולשאול "מה המתקן הכי שווה בפארק?". קל לראות בעין שהערך של המתקן הכי מוצלח הוא 103. עכשיו נראה איך פייתון היה יכול למצוא את זה:

def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)

הפונקציה מקבלת צומת בעץ ומתפקדת כ Generator, היא מדווחת על הערך שהיא רואה ואז ממשיכה את הטיול בפארק וכל פעם ממשיכה לדווח על ערכים. רק זה מספיק בשביל שפייתון יוכל להדפיס את הערך הגדול ביותר בעץ:

print(max(dfs(root)))

עכשיו נסו אתם שני תרגילי הרחבה:

  1. הקוד שכתבתי לא יתמודד טוב עם עץ שחלקים ממנו חוזרים על עצמם. אני יודע בלונה פארק זה יהיה מוזר לדמיין קטע של הפארק משוכפל לכמה שבילים, אבל במקומות אחרים זה יכול לקרות. נסו לחשוב מה יקרה אם אחד הצמתים יופיע בכמה מקומות ואז לתקן את הקוד.

  2. בטיול אמיתי בלונה פארק אם יש מתקן קרוב אליי שערכו 70 ומתקן ממש רחוק שערכו 75, אני אעדיף את המתקן הקרוב. נסו לחשוב איך להוסיף את המרחק לחישוב ומצאו את המתקן הכי אטרקטיבי כשלוקחים בחשבון את המרחק.

הקוד המלא שכתבתי כתוכנית אחת למי שרוצה לקחת כבסיס לתרגילי ההרחבה הוא:

from dataclasses import dataclass
from typing import List


@dataclass
class Node:
    value: int
    children: List['Node']


def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)


root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])

print(max(dfs(root)))
print("---")

for v in dfs(root):
    print(v)

עבודה אמיתית ולימוד

19/03/2021

אם הייתי מתחיל ללמוד היום פיתוח Full Stack והייתי צריך לעבור מאפליקציית Node.JS שכתבתי שעובדת אצלי על המכונה להתקנה של האפליקציה בענן, אין שום סיכוי שהייתי הולך ללמוד איך להקים שרת Linux מאפס. עזבו, אפילו היום שאני יודע להקים לעצמי שרתים, כשאני רואה את הממשקים של AWS או Azure DevOps או אפילו Heroku אני מרגיש טיפש להתעסק עם התקנות בעצמי. בלחיצת כפתור מתוך ה IDE היום אפשר להעביר את כל הקוד שלכם לשרת ברשת ולקבל עליו אינסוף מנגנוני ניטור וניהול.

או בדוגמה אחרת - אם הייתי מתחיל היום ללמוד git והייתי צריך להתחבר למאגר בשביל לסנכרן עבודה עם חברים בצוות, אין שום סיכוי שהייתי מתחיל מללמוד את ממשק שורת הפקודה ולקרוא את ה Git Book. יש כלי GUI כל כך טובים שכבר מסונכרנים עם ה IDE שלי כך שבלחיצת כפתור מה IDE אני יכול לעשות את כל הפעולות שאני צריך.

הבחירה ללמוד טכנולוגיה בצורה משמעותית ולעומק היא לא דרישת קדם לפני שמתחילים לעבוד בטכנולוגיה. למעשה זה לא הגיוני להשקיע כל כך הרבה זמן לימוד לפני שאתם מתחילים להיות מועילים. הבוס משלם לכם כסף כדי שתפתרו באגים ותכתבו פיצ'רים, לא בשביל שתהיו הרבה יותר מקצועיים ב Linux ממה שצריך בשביל העבודה שלכם.

ומהעובדות האלה אנחנו יכולים להתקדם לשני כיוונים אפשריים:

  1. אפשר לחפש ללמוד את המינימום שאני צריך בשביל להתקדם בעבודה. אם אפשר לעבוד עם ה GUI של גיט ולהגיע לתוצאות טובות אז כאן אני עוצר. אין טעם ללמוד מעבר לזה.

  2. אפשר לחפש ללמוד את המקסימום שאני יכול על כלי מסוים, מתוך עניין ובשביל לגלות עליו דברים שאולי יעזרו לשפר את העבודה שלי (ואולי לא). לימוד טכנולוגי כי אפשר, וכי אני מסוג האנשים שלומד טכנולוגיות חדשות.

אני חושב שהטענה שצריך לדעת הכל טוב בשביל להתקדם בעבודה לא מוכיחה את עצמה ב Scale. רוב המתכנתים שנפגוש לא מכירים את הקרביים של כל כלי שאנחנו משתמשים בו ובכל זאת אנחנו מצליחים לכתוב מוצרים לא רעים. הבחירה השניה, הבחירה ללמוד לעומק, לאתגר את עצמנו ולחפש דרכים יצירתיות לפתור בעיות לא צריכה את ההצדקה של התוצאה. היא טובה מספיק בפני עצמה.

משימות לימוד מעניינות

18/03/2021

בניסיון ללמוד טכנולוגיה חדשה ואחרי שקוראים את ה Tutorial חוזרת כמעט תמיד אותה השאלה: "מה לבנות עכשיו?". מצד אחד אם אתם רוצים ללמוד איך לבנות פרויקט Full Stack גדול ב Node.JS ו React ברור שדרך טובה תהיה לבחור נושא ולבנות עבורו פרויקט. אבל מצד שני פיתוח פרויקט שלם לוקח נצח ויש הרבה מאוד חלקים בפיתוח הפרויקט שלא קשורים ללימוד הטכנולוגיות שרציתם ללמוד.

דרך אחת להתמקד בתהליך הלימוד היא ליצור משימות לימוד מעניינות על הטכנולוגיה, כאשר כל משימה קשורה ללימוד היבט מסוים של הנושא ומוגבלת בזמן.

נניח בשביל הדוגמה שאנחנו רוצים ללמוד React ועברנו את ה Tutorial הבסיסי. עכשיו אני יכול להמשיך לדבר על Data Flow ב React ולהתחיל ליצור משימות לימוד יותר ממוקדות:

  1. אני רוצה ללמוד איך קומפוננטה שומרת State מספרי פשוט אז אבנה קומפוננטה של מונה לחיצות עם State.

  2. אני רוצה ללמוד איך קומפוננטה שומרת State יותר מורכב אז אלך לממש את Game Of Life בתור קומפוננטה יחידה.

  3. אני רוצה ללמוד איך לטפל באירועים אז אלך לכתוב משחקון בו צריך ללחוץ על כדור שזז על המסך.

  4. אני רוצה ללמוד איך כמה קומפוננטות מתקשרות ביניהן אז אלך לבנות דף עם מספר מוני לחיצות וקומפוננטה שמציגה את סכום הערכים בכל המונים.

הרבה אנשים מסתכלים על רשימת משימות כזאת ומרגישים שמשהו חסר, שגם אם אצליח לפתור המון המון משימות קטנות זה לא יהפוך אותי למתכנת Full Stack שיודע לכתוב מערכות גדולות או למתכנת React שמעסיקים רוצים לגייס. עדיין יישאר פער בין משהו שיודע לפתור בעיה ספציפית ב React לבין משהו שיודע לבנות מערכת גדולה.

והתחושה הזאת היא מעולה כי הרגע היא פתחה לי את הדלת לאוסף חדש של משימות לימוד, הפעם כאלה שלא בהכרח קשורות לריאקט:

  1. אני רוצה ללמוד איך נראית מערכת Web המשלבת קוד צד-שרת עם קוד צד-לקוח ומעבירה מידע באמצעות REST API; אז אלך לבנות מערכת של דף בודד שממחישה את החיבור הזה בכל מיני טכנולוגיות צד-לקוח וכל מיני טכנולוגיות צד שרת.

  2. אני רוצה ללמוד איך להשתמש ב Client Side Router אז אלך לבנות מערכת המורכבת מ-5 דפים שמעבירים מידע ביניהם, ואבנה אותה 4 פעמים כל פעם בטכנולוגיה אחרת.

התאמה טובה בין הדבר שאני רוצה ללמוד לכלי שאני בוחר ללמוד באמצעותו, ודיוק בהגדרת משימת הלימוד, הם דברים שיכולים להפוך את הלימוד שלנו להרבה יותר ממוקד ולהרבה יותר יעיל.