ToCode
Открыть в Telegram
1 419
Подписчики
Нет данных24 часа
Нет данных7 дней
Нет данных30 день
Архив постов
1 419
# אין בעיה של מקום
גוגל הבינו כבר מזמן שמגבלת המקום על מיילים היא פיקטיבית. הערך הוא לא בלתת ללקוח יותר מקום איחסון אלא בלתת ללקוח דרך יותר טובה למצוא את הדברים שלו - וכך גוגל תמונות, גוגל כונן וכמובן ג'ימייל והחיפוש, הופכים למכונות לאיתור מידע.
אבל למגבלת המקום (או יותר נכון לחוסר המגבלה) כן יש מחיר, והוא הפרטיות והשליטה שלנו הלקוחות על המידע שנשמר עלינו.
כשאין מגבלה של מקום הוט יכולים לענות לטלפון ולמרות שלא הייתי לקוח שלהם מעל עשר שנים הם יודעים בדיוק את כל הפרטים עליי (נכון ללפני עשר שנים). למעשה הם יודעים את זה הרבה יותר טוב ממה שאני מצליח לזכור בעצמי בגלל הזמן שעבר.
עד ה GDPR, וגם היום מחוץ לאירופה, הגישה בחברות רבות לנתונים היא "כמה שיותר יותר טוב", והגישה לשקיפות היא "כמה שפחות יותר טוב". זה אומר שהם ישמרו עליך כמה מידע שהם רק יכולים, ויספרו לך הכי מעט ממנו שהם רק יכולים. הכל בשביל חלום שיום אחד נעשה משהו עם כל המידע הזה.
אבל מעבר לבעיה של הפרטיות, שמירת אינסוף נתונים לנצח היא פשוט בזבוז. הרבה יותר משתלם לחברות וגם ללקוחות להיות ממוקדים וברורים -
1. אנחנו נשמור עליך נתונים X, Y ו Z.
2. הנתונים יישמרו לתקופה של Y שנים.
3. אנחנו נשתמש בנתונים כדי להשתפר במטריקה M.
4. אתה יכול לבקש שנתונים מסוימים יימחקו דרך הקישור הזה.
הקפדה על ארבעת הכללים כשאנחנו אוספים מידע על לקוחות עוזרת לבנות חברות ממוקדות יותר ולקוחות מרוצים יותר.
1 419
# אין בעיה של מקום
גוגל הבינו כבר מזמן שמגבלת המקום על מיילים היא פיקטיבית. הערך הוא לא בלתת ללקוח יותר מקום איחסון אלא בלתת ללקוח דרך יותר טובה למצוא את הדברים שלו - וכך גוגל תמונות, גוגל כונן וכמובן ג'ימייל והחיפוש, הופכים למכונות לאיתור מידע.
אבל למגבלת המקום (או יותר נכון לחוסר המגבלה) כן יש מחיר, והוא הפרטיות והשליטה שלנו הלקוחות על המידע שנשמר עלינו.
כשאין מגבלה של מקום הוט יכולים לענות לטלפון ולמרות שלא הייתי לקוח שלהם מעל עשר שנים הם יודעים בדיוק את כל הפרטים עליי (נכון ללפני עשר שנים). למעשה הם יודעים את זה הרבה יותר טוב ממה שאני מצליח לזכור בעצמי בגלל הזמן שעבר.
עד ה GDPR, וגם היום מחוץ לאירופה, הגישה בחברות רבות לנתונים היא "כמה שיותר יותר טוב", והגישה לשקיפות היא "כמה שפחות יותר טוב". זה אומר שהם ישמרו עליך כמה מידע שהם רק יכולים, ויספרו לך הכי מעט ממנו שהם רק יכולים. הכל בשביל חלום שיום אחד נעשה משהו עם כל המידע הזה.
אבל מעבר לבעיה של הפרטיות, שמירת אינסוף נתונים לנצח היא פשוט בזבוז. הרבה יותר משתלם לחברות וגם ללקוחות להיות ממוקדים וברורים -
1. אנחנו נשמור עליך נתונים X, Y ו Z.
2. הנתונים יישמרו לתקופה של Y שנים.
3. אנחנו נשתמש בנתונים כדי להשתפר במטריקה M.
4. אתה יכול לבקש שנתונים מסוימים יימחקו דרך הקישור הזה.
הקפדה על ארבעת הכללים כשאנחנו אוספים מידע על לקוחות עוזרת לבנות חברות ממוקדות יותר ולקוחות מרוצים יותר.
1 419
# פיתרונות פשוטים, פיתרונות מסובכים וקיפאון
עבדתי לא מזמן עם קבוצת פיתוח שם המתכנתים כותבים הרבה יישומונים קטנים, כולם ב React כל אחד עושה משהו קצת אחר. כל פעם שמישהו צריך להתחיל יישומון חדש (קורה יחסית לעתים קרובות) הוא או היא מפעילים create-react-app ומעתיקים כמה שורות של קוד תבנית מיישומון קיים אחר וגם מוסיפים קצת משלהם והם מוכנים לעבוד.
מיד הצעתי להם לנסות ליצור לעצמם תבנית ל create-react-app שתכלול את קוד התבנית שהם צריכים. אחרי בדיקה הסתבר שיש כמה דברים שהם צריכים שתבנית של create-react-app לא ממש יכולה לתת, ובשביל לבנות מנגנון כזה הם יצטרכו עכשיו לעשות התאמה לסקריפטים של cra או להוסיף עוד מנגנונים גנריים משלהם - ובקיצור סיפור. הם אמרו שינסו, אני חזרתי לעזור להם בדבר שבשבילו באתי ונפרדנו לשלום.
כמה חודשים אחרי זה נתקלתי בהם שוב - נו, שאלתי, "איך הלך עם ההתאמה של create-react-app?", והתשובה היתה קצת מפתיעה. החלק הצפוי הוא שהם באמת ניסו את הרעיון שלי לעשות התאמה של cra לצרכים שלהם וויתרו על זה די מהר. העסק היה מסובך מדי ותמיד היו דברים חשובים יותר לעשות. ברור שזה אחלה פיתרון אם זה היה עובד, אבל הוא דורש יותר מדי עבודה.
החלק המפתיע היה שבמקום להיכנס לקיפאון ולהמשיך להעתיק ביד קטעים מיישומון ליישומון, מישהו שם החליט להראות שהוא יודע יותר טוב וכתב סקריפט פשוט שבונה יישומון חדש. אין לזה את כל היכולות הגנריות של create-react-app, אבל זה מייצר להם בדיוק את התבנית שהם צריכים ומתממשק בדיוק למנגנונים שיש אצלם בקבוצה. והבונוס הוא שכתיבת כל העסק לקחה שעתיים במקום שבועות.
ותוך כדי שאני מקשיב לסיפור ורואה איך הסקריפט המאוד פשוט וספציפי הזה פתר להם בעיה אמיתית, יש לי קול כזה בתוך הראש שצועק "לא, זה לא הפיתרון הנכון, זה לא מספיק גנרי, מה אם תצטרכו ..."
אבל הם לא היו צריכים.
והקול הזה שרוצה את הפיתרון הכי מסובך, הכי גנרי והכי נכון הוא לפעמים הקול שמוביל אותנו לקיפאון - בזמן שאנחנו מחכים שיהיה זמן לממש את הפיתרון הכי נכון. במצב כזה ואם אפשר, פיתרון ביניים קצר וספציפי יכול להיות בדיוק זריקת המרץ שהיינו צריכים.
1 419
# חמש תשובות יותר טובות מ"נראה אחלה"
כשאת מסיימת עבודה והלקוחה (ראש צוות, משתמשת, מנהלת מוצר, מי שלא תהיה הלקוחה) אומרת "נראה אחלה" זה משהו שנותן הרגשה טובה באותו רגע, אבל לאורך זמן ה"נראה אחלה" הזה מוביל לקיפאון. מתכנתים ומתכנתות שרוצים לבלוט יעשו טוב אם יצאו מאזור הנוחות וינסו להשיג פידבק יותר מעניין, למשל:
1. מתי הספקת לעשות את כל הריפקטורינג הזה?
2. בשביל מה היית צריכה לגעת במודול ההוא - הוא עבד מעולה קודם
3. וואו איך הכל נטען כל כך מהר!
4. למה לקח כל כך הרבה זמן לבנות כזה פיצ'ר פשוט? אנחנו לא צריכים את כל הבדיקות האלה בשביל זה יש QA
5. התיעוד נראה מעולה
וכן קל לראות שלא כל התשובות פה חיוביות. מי שמנסה לעשות עבודה מעולה גם יפשל יותר, ולפעמים המעולה יבוא על חשבון ה"טוב מאוד". וגם קל לראות (ואולי קצת מבאס) שרוב המקומות שנגיע אליהם לא מחפשים רמה מקצועית כמה שיותר גבוהה אלא מכוונים לנקודת איזון מסוימת - כי יותר גבוה ממנה זה כבר לא משתלם עסקית.
המשחק שלנו בקריירה הוא תמיד למצוא את המקום שיעזור לי להשתפר במטריקה מסוימת, וכשהניסיון לקפוץ יותר גבוה מפסיק להתקבל באהבה לחתוך ולחפש את המקום הבא. ברוב מקומות העבודה יש דבר כזה "כאן יותר מדי זמן".
1 419
# סיבות (ששמעתי במקרה) להיות עצמאי
- רק בתור עצמאי הצלחתי לעבוד במה שאני רוצה באמת
- בתור שכיר במקצוע שלי הייתי מרוויח רבע
- רציתי לבחור איזה פרויקטים אני לוקח ועל מה אני עובד
- רציתי לבחור עם איזה אנשים אני עובד (ועם מי אני לא עובד)
- נמאס לי שאני עושה את כל העבודה והבוס לוקח את כל הקרדיט
- חיפשתי דרך לבלות יותר זמן עם הילדים
- חיפשתי הכנסה נוספת
- תמיד חלמתי להקים עסק שיצליח בגדול
- כולם במשפחה שלי עצמאיים
לא משנה מה הסיבה או הסיבות שהיו לכם כשהתחלתם, שווה לזכור שמותר להחליף. החיים משתנים וגם אנחנו. ולפעמים להחליף את הסיפור זה הצעד הראשון ביצירת מציאות חדשה.
1 419
"vhost": "/",
"durable": true,
"auto_delete": false,
"arguments": {
"x-queue-type": "classic"
}
}
],
"exchanges": [
{
"name": "loggers",
"vhost": "/",
"type": "fanout",
"durable": true,
"auto_delete": false,
"internal": false,
"arguments": {}
}
],
"bindings": [
{
"source": "loggers",
"vhost": "/",
"destination": "messages",
"destination_type": "queue",
"routing_key": "logmessage",
"arguments": {}
}
]
}
אני שומר את הקובץ בתיקיה חדשה ולידו יוצר קובץ בשם Dockerfile עם התוכן הבא:
FROM rabbitmq:3
COPY 20-load-my-definitions.conf /etc/rabbitmq/conf.d/
COPY definitions.json /etc/rabbitmq/
RUN chown rabbitmq:rabbitmq /etc/rabbitmq/definitions.json
ונשאר לנו רק להוסיף עוד קובץ אחד - קובץ הקונפיגורציה שטוען את ההגדרות מה json. נוסיף לתיקיה את הקובץ 20-load-my-definitions.conf עם התוכן הבא:
load_definitions = /etc/rabbitmq/definitions.json
כשכל הקבצים במקום אני יכול לבנות את האימג' עם:
$ docker build . -t my-rabbitmq-example
ומריץ עם:
$ docker run --rm -p 5672:5672 my-rabbitmq-example
עכשיו אם אני מנסה להתחבר עם קוד מהסוג הזה:
channel = connection.create_channel
x = channel.fanout('loggers')
אני אקבל שגיאה. בגלל שה Exchange כבר הוגדר אני חייב לציין בדיוק את הפרמטרים שאיתם ה Exchange הוגדר כדי שאוכל לעבוד איתו. התוכנית ששולחת הודעות לכן צריכה להשתנות והקוד שלה יראה כך:
#!/usr/bin/env ruby
require 'bunny'
connection = Bunny.new
connection.start
channel = connection.create_channel
x = channel.fanout('loggers', durable: true)
loop do
x.publish("#{rand} - Hello World", routing_key: :logmessage)
sleep 1
end
connection.close
שימו לב שבלוק יצירת החיבורים הפעם קצר יותר ומורכב רק משורת החיבור ל Exchange המתאים. את הקשר בין התור ל Exchange כבר יצרנו בשלב יצירת שרת ה RabbitMQ. מפתח הניתוב logmessage נלקח ישירות מההגדרות שיצרנו בזמן בניית האימג'.
גם בצד המקבל התוכנית יכולה לוותר על יצירת החיבור בין התור ל Exchange והקוד המקבל נראה כך:
#!/usr/bin/env ruby
require 'bunny'
connection = Bunny.new
connection.start
channel = connection.create_channel
queue = channel.queue('messages', durable: true)
begin
puts ' [*] Waiting for messages. To exit press CTRL+C'
queue.subscribe(block: true) do |_delivery_info, _properties, body|
puts " [x] Received #{body}"
end
rescue Interrupt =>
connection.close
exit(0)
end
## מתי נבחר בכל שיטה
היכרות עם שתי הגישות להגדרת שרת RabbitMQ עוזרת לנו לבחור את שיטת העבודה שמתאימה לנו בכל מצב:
1. כשיש לי מעט תוכניות שמשתמשות ב RabbitMQ, כשיש מעט חיבורים וכשאותם מתכנתים עובדים על הקוד של כל התוכניות (שכתובות באותה שפת תכנות) - יהיה לי יותר קל להשתמש בגישה הראשונה. אני אכתוב את קוד החיבור ל RabbitMQ בקובץ אחד ואטען אותו מכל תוכנית שצריכה להתחבר למערכת ההודעות. כשצריך לשנות משהו במבנה מספיק לשנות שם ולהפעיל מחדש את המערכת.
2. כשמערכת ההודעות משמשת לסינכרון בין מספר מערכות שנכתבות על ידי מספר צוותים, וככל שהקשרים בין התורים יהיו מסובכים יותר, כך כדאי לי לבנות את כל ההגדרות של RabbitMQ בנפרד וכך להכריח את התוכניות לעבוד לפי הקונפיגורציה שעליה החלטנו. מתכנתים בצוותים השונים יכולים להיות רגועים שהגדרות התור לא ישתנו פתאום, כי שינוי הגדרות הוא קשה יותר ודורש בניה מחדש של האימג'.
נ.ב. אם אתם אוהבים את RabbitMQ ורוצים לשמוע עוד רעיונות לעבודה יעילה באמצעותו, מוזמנים להירשם לוובינר שאעביר בנושא בתחילת החודש הבא בקישור:
https://www.tocode.co.il/workshops/1151 419
# שתי דרכים להגדיר תורים ב RabbitMQ
מערכת ניהול התורים RabbitMQ מציעה לנו שתי דרכים מרכזיות לבניית קונפיגורציה. במדריך זה נבנה תוכניות פשוטות לשליחה וקבלת הודעות ונראה מהן שתי הדרכים להגדרת התורים ומתי נרצה להשתמש בכל שיטה.
## מה אנחנו בונים
הדוגמה שלנו היום מורכבת משתי תוכניות - אחת כותבת הודעה ל RabbitMQ והשניה מקבלת הודעה ומדפיסה אותה למסך. אבל, התוכניות האלה מחוברות ב Fanout Exchange, מה שאומר שכל הודעה שנכתבת נשלחת אוטומטית לכל התוכניות שמקשיבות על התור. מבנה זה יאפשר לי להריץ מספר עותקים של התוכנית השניה ולקבל את ההודעות בכל עותק. בנוסף אוכל בעתיד להוסיף עוד תוכניות שקוראות והן יוכלו לעשות דברים אחרים עם ההודעות.
מבנה זה מתאים לדוגמה למערכת לוגים מבוזרת, שם תהליכים מוסימים יוכלו לכתוב הודעות לוג, ותהליכים אחרים יהיו אחראיים לכתיבת הודעות הלוג ליעד שלהן - למשל תהליך מסוים יכתוב את כל ההודעות לקובץ על הדיסק, תהליך אחר יכתוב את ההודעות לטבלה בבסיס הנתונים ואחר ישלח את הלוגים לאחסון בענן.
החלק בקוד בשפת רובי ששולח הודעות לתור נראה כך:
loop do
x.publish("#{rand} - Hello World")
sleep 1
end
והחלק בקוד בשפת רובי שקורא מהתור ומדפיס הודעות למסך הוא:
queue.subscribe(block: true) do |_delivery_info, _properties, body|
puts " [x] Received #{body}"
end
## הגדרת התורים בתוך קוד התוכנית
גישה ראשונה לבניית המערכת היא להגדיר את התורים בתוך קוד התוכנית. הייתרון בגישה זו הוא שהכל נמצא במקום אחד ואנחנו (המתכנתים) מרגישים בשליטה על המערכת. אם צריך לשנות משהו מספיק לשנות רק קוד. הקוד להקמת התורים בתוכנית ששולחת הודעות יהיה:
channel = connection.create_channel
queue = channel.queue('')
x = channel.fanout('loggers')
queue.bind(x)
וזה מביא אותנו גם לחיסרון בגישה - את אותו הקוד שמקים את התורים אנחנו צריכים לכתוב בכל אחת מהתוכניות שעובדות עם RabbitMQ. כלומר גם בתוכנית שקוראת הודעות נצטרך לכתוב בלוק כזה:
channel = connection.create_channel
queue = channel.queue('')
x = channel.fanout('loggers')
queue.bind(x)
ככל שיותר תוכניות יעבדו עם התורים וככל שהן כתובות ביותר שפות ומתוחזקות על ידי צוותים שונים, כך הגדרת הקשרים בין התורים בקוד הופכת פחות פרקטית. וזה מביא אותנו לגישה השניה-
## הגדרת התורים בתוך Docker Image
דרך נוספת להגדיר את התורים והקשרים ביניהם ב RabbitMQ היא לבנות אימג' של רביט שהתורים כבר מוגדרים בו. כאן יש לנו קצת יותר עבודה בהקמה אבל יהיה יותר קל לסנכרן בין תוכניות שונות.
אני אוהב להשתמש ב Management Plugin של RabbitMQ בשביל להגדיר את התורים והקשרים ביניהם דרך ממשק גרפי. אני מריץ שרת RabbitMQ שפלאגין הניהול שלו מופעל עם דוקר באמצעות הפקודה:
$ docker run --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management
עכשיו אני יכול להיכנס לממשק הניהול דרך הדפדפן בכתובת:
localhost:15672
התחברות עם משתמש וסיסמה guest ו guest תכניס אותי למסך ניהול התורים. שם אני מבצע את הפעולות הבאות:
1. בטאב Queues אני יוצר Queue חדש וקורא לו messages.
2. בטאב Exchanges אני יוצר Exchange חדש מסוג Fanout וקורא לו loggers. בתפריט bindings של ה Exchange אני יוצר Binding חדש, מחבר את ה Exchange לתור שיצרתי ובוחר בתור מפתח ניתוב את המילה logmessage.
3. בטאב Overview אני בוחר באפשרות Export definitions ולוחץ על כפתור Download broker definitios.
זה נתן לי קובץ JSON שנראה כך:
{
"rabbit_version": "3.9.15",
"rabbitmq_version": "3.9.15",
"product_name": "RabbitMQ",
"product_version": "3.9.15",
"users": [
{
"name": "guest",
"password_hash": "KpcShz9x5hNdr6gP7/4UAuZGM4hwt1HzI8ENYfnbBo5ZuyGG",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": [
"administrator"
],
"limits": {}
}
],
"vhosts": [
{
"name": "/"
}
],
"permissions": [
{
"user": "guest",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
}
],
"topic_permissions": [],
"parameters": [],
"global_parameters": [
{
"name": "internal_cluster_id",
"value": "rabbitmq-cluster-id-aUEEtfFz1z464iTHnlF0yw"
}
],
"policies": [],
"queues": [
{
"name": "messages",1 419
# אבל זה עבד לי בבוקר
תחזוקה של קוד היא לא רק תיקון באגים שכבר היו בו, אלא הרבה פעמים תיקון באגים חדשים שנוצרים כי העולם זז קדימה. לפעמים זה יהיה באג בספריה שאני תלוי בה שגורם למתחזקים של אותה ספריה להוציא גירסה חדשה ועכשיו אני צריך להשתדרג כדי להתאים להם. לפעמים זאת תהיה גירסה חדשה של מערכת הפעלה או סביבת הרצה שכבר לא מוכנים להריץ את הקוד שכתבתי.
קוד, כמו מכשיר חשמלי, מתקלקל כשלא משתמשים בו. אז אם יש לי מערכת שאני נוגע בה רק פעם בחצי שנה כדי לתקן משהו קטן, אותה נגיעה עלולה לעלות לי בבריאות כי לא בטוח שאני אפילו אצליח לקמפל את הקוד החדש.
הטיפים הבאים עוזרים לי לשמור את רוב הפרויקטים שלי במצב שאפשר לעבוד עליהם. אין 100% אבל נסו ואולי זה יעזור גם לכם:
1. החזיקו מכונת בניה נפרדת לכל פרויקט. שימו לב שאחרי שידרוג מערכת ההפעלה על מכונת הבניה היא עדיין מצליחה לבנות את הפרויקט. (לבנות - הכוונה להפוך את הפרויקט מקבצי קוד מקור למשהו שלקוח יכול לעבוד איתו).
2. שימרו לכל פרויקט סט בדיקות אוטומטיות ומכונה שיכולה להריץ בדיקות אלה.
3. הקפידו להעלות גירסה פעם בכמה חודשים, אפילו אם אין שינויים, רק בשביל לוודא שהמנגנון עדיין עובד.
4. אם תקפידו לשדרג את מכונת הבניה והתלויות לפחות פעם בכמה חודשים, אתם תראו שמדי פעם אתם מקבלים בבניה אזהרות על זה שפיצ'רים מסוימים שהשתמשתם בהם תכף לא ייתמכו. היו ערניים לאזהרות אלה ועדכנו את הקוד בהתאם וכשיש לכם זמן.
5. היו מעודכנים בשינויים שקורים בטכנולוגיות ובתלויות של הפרויקט שלכם. זה אומר להירשם לקבלת עדכונים מכל שירות צד שלישי או פריימוורק במערכת שלכם.
מערכות יכולות להפסיק לעבוד "פתאום" גם אם לא נגעתם בכלום וגם אם לא בניתם גירסה חדשה. זה קורה כי דפדפן השתדרג, כי שירות צד שלישי שעבדתם איתו נסגר או עדכן את ה API, כי הלקוח שלכם עדכן מערכת הפעלה או מאלף סיבות אחרות. שמירה על עירנות והקפדה על שידרוגים ועדכונים בזמן יעזרו לכם ולמערכות שלכם להמשיך לעבוד לאורך זמן.
1 419
# לאט או עם באגים
בהתלבטות על בדיקות אנחנו אוהבים לחשוב על הערך של כתיבת בדיקות בתור סקאלה, כשבצד אחד תהיה לי מערכת עם הרבה בדיקות שהפיתוח שלה יהיה יותר איטי אבל יהיו בה פחות באגים, ובצד השני פיתוח "נינג'ה" מהיר שתוך רגע נקבל מוצר אבל אולי יהיו בו באגים לא צפויים.
אבל המציאות לא עובדת ככה-
יש פרויקטים עם המון בדיקות שעדיין כוללים המון באגים. הרבה מתכנתים שעבדו על פרויקטים כאלה יספרו לכם שבדיקות זה כלי כמעט חסר ערך.
יש פרויקטים עם כמות טובה של בדיקות שנכתבים באותו קצב ולפעמים גם יותר מהר כמו פרויקטים בלי בדיקות בכלל.
יש פרויקטים בלי בדיקות בכלל שכמעט לא כוללים באגים או לפחות לא כוללים את הבאגים המוזרים האלה שקשה למצוא. כן, יש מתכנתים מעולים שלא כותבים בדיקות וגם לא כותבים באגים.
ויש לא מעט פרויקטים שלא כוללים בדיקות ועדיין לא מצליחים להתקדם ונתקעים על שטויות.
כתיבת בדיקות היא חלק מהרמה המקצועית שלנו כמתכנתים. בזכות הבדיקות אנחנו הרבה פעמים מבינים טוב יותר את הקוד שאנחנו כותבים. הבדיקות נותנות לנו מקום לדבר עליו בהיגיון ולענות על שאלות בצורה מבוקרת.
כתיבת בדיקות היא חלק מהרמה המקצועית של המוצר. היא מאפשרת תיעוד אמין יותר מ wiki או הערות בקוד, תיעוד חי שמתעדכן יחד עם המוצר שלנו ועוזר לוודא שאנחנו לא חוזרים על אותן טעויות.
וככל שכמתכנתים אנחנו רגילים יותר לכתוב בדיקות, וככל שהמוצר שאנחנו עובדים עליו כולל תשתית בדיקות טובה, כך כתיבת הבדיקות לא מאטה את הפיתוח ואפילו להיפך - היא מאפשרת לנו לשמור על קצב מהיר לאורך זמן.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
