ToCode
1.4K subscribers
3.25K links
טיפים קצרים למתכנתים מאת ינון פרק
Download Telegram
# היכולת לרצות
יותר מדי מתכנתים מסתכלים על בעיות היום יום והשריפות שיש כל יום בעבודה בתור כח טבע כמעט. סוג של "ככה זה עובד אצלנו". אם תלכו לדבר עם חבר או תשאלו את עצמכם אפילו מה היה אפשר לעשות טוב יותר? איך אפשר היה ליצור תהליכים טובים יותר או מערכות טובות יותר, תגלו שאתם מצליחים למצוא מעט מאוד רעיונות והם לא יצירתיים במיוחד.

אפילו שבמקומות אחרים ובחברות אחרות דברים עובדים אחרת לגמרי.

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

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

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

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

זד שו עשה את זה בפייתון עם הספר שלו learn python the hard way. בדומה לדואולינגו, הספר זכה להצלחה מסחררת כי אנשים שקראו אותו הצליחו להתחיל לתכנת לפני שלמדו את הכללים, פשוט באמצעות ביצוע פרויקטים שאי אפשר לטעות בהם. אם יום אחד אני אכתוב ספר תכנות למתחילים הוא בוודאות יהיה בגישה זו.

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

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

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

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

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

מנגנון ה Error Boundaries של ריאקט מאפשר לבנות סוג של בלוקים עבור try ו catch בתוך קומפוננטות: אנחנו מגדירים קומפוננטה שהיא Error Boundary, אותה קומפוננטה תופסת שגיאות שקרו בתוך ה render של הילדים שלה ויכולה להציג ממשק משתמש חלופי עם הודעת שגיאה נחמדה ואפשרות לחזור לעבוד באפליקציה.

ההצעה מהתיעוד של ריאקט למימוש מחלקה Error Boundary נראית כך:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}


ואפשר להשתמש בזה כדי לעטוף קומפוננטה רגילה בצורה הבאה:

<ErrorBoundary>
<MyWidget />
</ErrorBoundary>


ביישומים שמשתמשים ב React Router אנחנו רוצים לאפס את השגיאה בצורה אוטומטית כשמשתמש עובר לנתיב אחר, כמו שהיה קורה אם המשתמש היה לוחץ על קישור רגיל לדף אחר. שינוי קטן ב Error Boundary יפתור את הבעיה:

1. אעטוף את המחלקה ב withRouter כדי לקבל גישה למאפיין location.

2. אוסיף למחלקה מימוש ל componentDidUpdate שיבדוק אם המיקום השתנה.

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

הקוד אחרי התיקון נראה כך:

const ErrorBoundary = withRouter(class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidUpdate(prevProps, prevState) {
if (this.props.location !== prevProps.location) {
this.setState({ hasError: false });
}
}

componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
// logErrorToMyService(error, errorInfo);
console.log(error, errorInfo);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div>
<h1>Something went wrong.</h1>
<a href='/'>Back home</a>
</div>
);
}

return this.props.children;
}
});
# דוגמא קצרה למימוש Web Spider ב Python
אחד התרגילים שאני אוהב לתת כשאני מלמד Multi Threaded Programming בשפת Python הוא פיתוח של Web Spider - או בעברית כלי שמקבל דף אינטרנט ומתחיל ממנו לחפש ולהוריד את כל דפי האתר. העכביש מתחיל בעמוד הראשון, מחפש שם לינקים לדפים נוספים, מוריד אותם ואז ממשיך לחפש בהם לינקים לדפים נוספים.

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

הנה דוגמא קצרה של פיתרון באמצעות Threads, ואחריה הסבר על הנקודות המרכזיות:

import threading
from threading import Thread, Lock
from queue import Queue, Empty
from urllib.parse import urlparse, urlunparse
from pathlib import Path
from bs4 import BeautifulSoup
import requests

base = 'https://www.python.org'
path = '/dev/peps/'
dest = Path('./pydoc')
found_links = set()
found_links_lock = Lock()
q = Queue()

tls = threading.local()

def run(q):
tls.session = requests.Session()
while True:
href = q.get()
if href is None:
return

with found_links_lock:
if href in found_links:
print(f"Found {href} that was already downloaded")
q.task_done()
continue

found_links.update([href])

discover(href)
q.task_done()


def download(url, to):
r = tls.session.get(url, allow_redirects=True)
open(to, 'wb').write(r.content)


def parse(htmlfile):
with open(htmlfile) as f:
soup = BeautifulSoup(f.read(), 'lxml')
new_links = set([
a['href'] for a in soup.find_all('a')
if a['href'].startswith('/dev/peps') and
a['href'] not in found_links])

for link in new_links:
q.put(link)

print(f"Parse done - added {len(new_links)} new tasks")


def discover(href):
try:
url = urlparse(href)
srcpath = url.path

if not srcpath.startswith(path):
print("Path out of scope - ignore: ", href)
return

if srcpath.endswith('/'):
destpath = Path(dest) / Path(srcpath[1:]) / 'index.html'
else:
destpath = Path(dest) / Path(srcpath[1:])

destpath.parents[0].mkdir(parents=True, exist_ok=True)

if url.netloc == '':
href = base + href

download(href, destpath)
parse(destpath)

except Exception as e:
print("Failed to discover href ", href, e)
raise e


def main():
found_links.add('/dev/peps/')
start = 'https://www.python.org/dev/peps/'
q.put(start)
workers = [Thread(target=run, args=(q,)) for _ in range(4)]
for w in workers: w.start()
q.join()
for _ in range(4):
q.put(None)

for w in workers: w.join()


main()


מה הקוד עושה בגדול:

1. הפונקציה download מורידה קובץ מהרשת ושומרת אותו. בשביל שהפונקציה תעבוד יחסית מהר אני משתמש ב requests.Session. בגלל ש requests.Session לא עובד טוב בין תהליכונים לכל תהליכון יש Session משלו שנשמר באמצעות Thread Local Storage.

2. הפונקציה parse מקבלת קובץ html ומחפשת בו קישורים. כל פעם שמצאה קישור חדש היא מוסיפה אותו לתור.

3. הפונקציה המרכזית היא discover. פונקציה זו לוקחת קישור מהתור, מורידה אותו ומפעילה עליו את parse כדי לחפש בו קישורים.

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

5. בקוד הראשי main אני יוצר את התהליכונים ושולח אותם לדרכם. לאט לאט הם ימשכו קישורים מהתור ויוסיפו קישורים חדשים. הפקודה q.join מחכה עד שהתור יתרוקן וגם כל הקישורים שכעת בעבודה יסתיימו ורק אז חוזרת. שליחת None לכל התהליכונים מוציאה אותם מהלולאה כדי לסיים את התוכנית.
# טיפ פייתון: זיהוי אות באמצעות ביטוי רגולרי
התמיכה של פייתון בביטויים רגולאריים היא לא רעה אבל גם לא טובה במיוחד. לדברים לוקח זמן להיכנס והיא כמעט תמיד מרגישה קצת מאחורי שפות כמו Ruby או אפילו perl בכל מה שקשור לחיפושים על טקסטים. דוגמא פשוטה לזה היא (חוסר) היכולת לחפש Character Properties ב Unicode.

בעברית Character Properties אומר שאנחנו מחפשים תו לפי מאפיין מסוים, בלי להתחייב לשפה. למשל אני יכול לחפש אות במחרוזת בלי שיהיה אכפת לי אם מדובר באות באנגלית או בעברית. ה Property הוא מאפיין של קבוצת תווים וחיפוש לפיו הוא חוצה שפות. אפשר למצוא רשימה של המון Unicode Properties בקישור הזה:
http://unicode.org/reports/tr44/#Properties.

אז בפייתון לא מצאתי תמיכה מובנית בחיפוש לפי מאפייני יוניקוד, אבל כן הצלחתי למצוא מודול בשם regex שמאפשר לשלב Character Properties בתוך הביטויים הרגולאריים שאנחנו כותבים ב Python. במשחק שלנו אם מה שאני רוצה זה למצוא אות בתוך מחרוזת אוכל להשתמש בקוד הבא:

In [2]: regex.search(r'\p{L}', 'hello')
Out[2]: <regex.Match object; span=(0, 1), match='h'>


ואנחנו רואים שבמילה hello יש אות. אותו ביטוי מתאים גם לשפות אחרות:

In [4]: regex.search(r'\p{L}', 'שלום')
Out[4]: <regex.Match object; span=(0, 1), match='ש'>

In [5]: regex.search(r'\p{L}', 'привет')
Out[5]: <regex.Match object; span=(0, 1), match='п'>


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

In [3]: regex.search(r'\p{L}', '💩')
# ניבים
בשפה מדוברת ניב הוא וריאציה נבדלת של השפה המשמשת קבוצה מסוימת באוכלוסיה, כך לפי ויקיפדיה. גם בתכנות אנחנו יכולים לזהות תבניות פיתוח שמתאימות לפרויקטים מסוימים ותבניות אחרות שיופיעו במקומות אחרים - עד כדי כך שמתכנתת JavaScript שמכירה ניב מסוים עשויה להרגיש אבודה כשתגיע לפרויקט JavaScript בו מדברים בניב אחר.

קחו אפילו דוגמא פשוטה של לולאה. בניב מסוים של JavaScript אנחנו יכולים למצוא את הקוד הבא:

const parent = document.getElementById('parent');
let childNodes = parent.childNodes;
let childNodesCount = childNodes.length;

for (let i=0; i < childNodesCount; i++) {
console.log(childNodes[i].textContent);
}


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

const parent = document.getElementById('parent');
let childNodes = parent.childNodes;

for (let i=0; i < childNodes.length; i++) {
console.log(childNodes[i].textContent);
}


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

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

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

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

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

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

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

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

PASSWORDS = {
'one' => 'secret',
'two' => 'number'
}

def check_password(username, password)
PASSWORDS.key?(username) && PASSWORDS[username] == password
end


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

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

$count = 0

def tick
$count += 1
end

tick
tick
tick
puts $count


והתוכנית הבאה נעזרת ב $_ כדי לחסוך משתנה:

while gets
next if /^d/
print
end


התוכנית תדפיס את כל השורות פרט לאלה שמתחילות ב d: תחילה היא לוקחת טקסט מהמשתמש באמצעות gets. הטקסט יישמר אוטומטית במשתנה הגלובאלי $_, לאחר מכן היא בודקת אם $_ מתאים לביטוי הרגולרי (ואם כן מדלגת לקרוא עוד שורה) ולבסוף מדפיסה את תוכן המשתנה $_. וכן אם זה נשמע לכם קצת כמו השראה מפרל אתם כנראה צודקים.

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

class Ticker
@count = 0

class << self
attr_accessor :count
end
end

def tick
Ticker.count += 1
end

tick
tick
tick
puts Ticker.count


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