ארכיון

ארכיון של אפריל, 2010

Startable – State Machine part 2

16 אפריל, 2010 תגובה אחת

בפוסט הזה אני אמשיך ב state machine שהצגתי כבר קודם, והפעם אני אצור abstract class שיכול להיות שימושי כאשר רוצים רכיב שרץ ברקע (נניח, רכיב שמציג מניות, רכיב שמאזין לבקשות TCP וכד').
מטבע הדברים, רכיב שכזה רץ על thread משלו, והקריאה למתודת ה Start שלו, ואח"כ גם ל Stop – יכולות להגיע מ threadים שונים, ולכן נצטרך לדאוג לנעילות.

בפשטות

נתחיל מהמקרה הפשוט יותר, בלי התייחסות לענייני multi-threading.
בואו נקפוץ פנימה לקוד ונסביר מה קורה:

public abstract class StartableBase
{
    private readonly IStartableStateMachine innerStateMachine;

    protected StartableBase()
        : this(StartableStatus.Stopped)
    {
    }

    protected StartableBase(StartableStatus initialStatus)
        : this(new StartableStateMachine(initialStatus))
    {
    }

    protected StartableBase(IStartableStateMachine innerStateMachine)
    {
        if (innerStateMachine == null) 
            throw new ArgumentNullException("innerStateMachine");
        this.innerStateMachine = innerStateMachine;
        innerStateMachine.OnStart += OnStart;
        innerStateMachine.OnStop += OnStop;
    }

    protected abstract void OnStart();
    protected abstract void OnStop();

    public void Start()
    {
        innerStateMachine.Start();
    }

    public void Stop()
    {
        innerStateMachine.Stop();
    }

    public StartableStatus Status
    {
        get { return innerStateMachine.Status; }
    }
}

סקירה קצרה:
יש כאן abstract class, כלומר הכוונה היא שיהיה class שיורש מה class שלנו.
מי שמכיר אותי כבר יודע שאני לא מת על ירושה בקוד, אבל כאן זה מתבקש, כי זה באמת מעודד שימוש חוזר בקוד, ומצד שני די קל להבין מה הולך פה.
בכל מקרה, בקלות אפשר לעקוף ירושה ע"י composition, אבל לא נרחיב על זה כאן. בשביל זה יש גוגל.
בקיצור, ה class הזה חושף לעולם שתי מתודות: Start ו Stop.
בנוסף, ב class יש קונסטרקטורים שבסופו של דבר נסמכים על state machine חיצוני, שבו נמצא כל הקוד למעברים בין המצבים.
התפקיד של ה class הוא להעביר את הקריאות של ה Start וה Stop החיצוניות שלו אל ה state machine הנ"ל. ההתנהגות של ה state machine, עם ה events שלו, מתגלגלת ל class היורש, באמצעות מתודות אבסטרקטיות.
במילים אחרות, כל התפקיד של ה class הזה הוא לעטוף את ההתנהגות של ה state machine ולהפוך אותה ל abstract class. סבבצ'יק.

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

public class MyCoolService : StartableBase
{
    protected override void OnStart()
    {
        Console.WriteLine("hey, now I should start working!");
    }

    protected override void OnStop()
    {
        Console.WriteLine("ok, now I should stop.");
    }
}

אכן, קוּל למהדרין. ככה משתמשים:

static void Main(string[] args)
{
    var myCoolService = new MyCoolService();
    Console.WriteLine("myCoolService.Status = " + myCoolService.Status);
    myCoolService.Start();
    Console.WriteLine("myCoolService.Status = " + myCoolService.Status);
    myCoolService.Stop();
    Console.WriteLine("myCoolService.Status = " + myCoolService.Status);
}

וזו התוצאה:

myCoolService.Status = Stopped
hey, now I should start working!
myCoolService.Status = Started
ok, now I should stop.
myCoolService.Status = Stopped

טוב, זה היה רק כדי לקבל תחושה, זה לא באמת קול.

Threads Ahoy!

זה מתחיל להיות מעניין כשעובדים עם יותר מ thread אחד.
אז גם כאן אולי כדאי שנתחיל מ abstract class, שמתבסס על הקודם (בירושה! סלח לי אבי כי חטאתי!), ונקפוץ לקוד:

/// <summary>
/// Marks the implementation as a thread-safe one. 
/// Nothing more than that.
/// </summary>
public interface IThreadSafeStateMachine : IStartableStateMachine
{
}

public abstract class ThreadSafeStartableBase : StartableBase
{
    protected ThreadSafeStartableBase() : 
        this(StartableStatus.Stopped)
    {
    }

    protected ThreadSafeStartableBase(StartableStatus initialStatus) : 
        this(new StartableStateMachineInterlock(initialStatus))
    {
    }

    protected ThreadSafeStartableBase(IThreadSafeStateMachine innerStateMachine)
        : base(innerStateMachine)
    {
    }
}

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

public class MyThreadSafeService : ThreadSafeStartableBase, IDisposable
{
    private readonly TimeSpan period;
    private readonly object syncLock;
    private readonly Timer timer;
    private int counter = 0;

    public MyThreadSafeService(TimeSpan period)
        : this(period, new object())
    {
    }

    private MyThreadSafeService(TimeSpan period, object syncLock)
        : base(new StartableStateMachineSimpleLock(StartableStatus.Stopped, syncLock))
    {
        this.period = period;
        this.syncLock = syncLock;
        timer = new Timer(MyTimerCallback);
    }

    private void MyTimerCallback(object state)
    {
        int current;
        lock (syncLock)
        {
            counter++;
            current = counter;
        }
        Console.WriteLine("Ping! so far {0} pings", current);
    }

    public int Counter
    {
        get
        {
            lock (syncLock)
            {
                return counter;
            }
        }
    }

    protected override void OnStart()
    {
        counter = 0;
        timer.Change(TimeSpan.Zero, period);
    }

    protected override void OnStop()
    {
        timer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
    }

    public void Dispose()
    {
        Stop();
        timer.Dispose();
    }
}

הקריאה:

static void Main(string[] args)
{
    var period = new TimeSpan(0, 0, 1); // ping every second
    var myThreadSafeService = new MyThreadSafeService(period);
    myThreadSafeService.Start();
    Console.ReadLine();
    myThreadSafeService.Stop();
    Console.WriteLine("Counter = " + myThreadSafeService.Counter);
    myThreadSafeService.Dispose();
}

והתוצאה (אחרי בערך 5 שניות):

======== Thread Safe Example ===========
Ping! so far 1 pings
Ping! so far 2 pings
Ping! so far 3 pings
Ping! so far 4 pings
Ping! so far 5 pings

Counter = 5
=========== End of Example =============

press --enter-- to quit

וואלה עובד.

סיכום

מי שכותב מדי פעם רכיב שפועל ברקע, כנראה מצא את עצמו חוזר על קוד התשתית של "להתחיל משהו, לדאוג לנעילות, לסנכרן סטטוס" וכו'.
בשני הפוסטים האלה הראיתי איך אפשר לכתוב base class שמעודד שימוש חוזר בקוד מהסוג הזה.
היתרון העיקרי הוא שאפשר להפריד בין המנגנון הפנימי של ה state machine וגם, באופן חלקי, לנעילות המתבקשות.
מצד שני, הירושה יכולה לגרום לכאב ראש כשנכנסים לדיבאג (או כשסתם מנסים להבין את זה).
בנוסף, יש לנו לא מעט קבצים לתחזק, רק כדי לכתוב קוד "נכון".
מה המסקנה? לא תמיד יש מסקנה חד משמעית, צריך לנסות ולראות אם זה מתאים לכם.
הערות והארות, או סתם תגובות יתקבלו בברכה. ובכלל, מי שקרא עד הלום – סחתן, כה לחי וישר כוח 🙂

אפשר להוריד את כל הקוד, כמובן.

קטגוריות:תכנות תגיות:, ,

A Few Words on StackOverflow.com

הלוגו של stackoverflow

הפוסט הזה הוא על האתר stackoverflow.com (ויקיפדיה), אתר שהפך להיות אחד המרכזיים והפעילים בתחום פיתוח תוכנה וכל מה שמסביב, בסגנון של מישהו-שואל-שאלה-ומלא-אנשים-עונים-עליה. או, בניסוח שלהם: Stack Overflow is a programming Q & A site that's free. איך זה שהאתר הזה הפך להיות כל כך פופולרי תוך זמן קצר? ואולי, לצד ההצלחה, יש לו גם כמה צדדים פחות טובים?

נתחיל מההצלחה עצמה: האתר הפך להיות ה-אתר ה-מוביל בעולם בשאלות/תשובות בענייני תכנות, וכל זאת כמעט מיד עם השקתו. האתר כולל מערכת ניקוד עשירה (שמזכירה פה ושם את המתחרה שנשאר הרחק מאחור, experts-exchange), ונכון להיום, הוא במקום 545 לפי מדד אלקסה. כבוד.

אז מה כל כך טוב ב stackoverflow הזה?

נו, זה פשוט נראה טוב

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

כן, וגם חוויית משתמש טובה

אין ספק, החבר'ה ב stackoverflow עשו עבודה טובה בהיבט של User Experience, ובכל הקשור לתגובתיות ולמהירות הניווט – זה פשוט עובד, ועובד נפלא. השימוש ב ajax הופך את העניינים לפחות "תהליכיים" והכל זורם יותר טוב (לדוגמה – הצמדת תגים לשאלה חדשה זה עניין ממש פשוט, גם כאשר מדובר בתג חדש).

אה כן, אין שם קטגוריות, הכל תגיות

בשנים האחרונות חל מפנה בארגון המידע, ובמקום היררכיה של קטגוריות (נניח קטגוריית "מסדי נתונים" ושם תתי-קטגוריה של "Oracle" ו"MS SQL Server"), ב stackoverflow יוצרים תגיות. אמנם, גם "תגית" וגם "קטגוריה" מסווגים את המידע ומאפשרים סינון, אלא שמידע מבוסס תגיות הוא שטוח, ואין צורך להסתגל להיררכיית סיווג מידע שחשב עליה עורך תוכן או מידען כזה או אחר. אם נרצה, למשל, לצפות בשאלות שעוסקות אך ורק ב SMTP וב Oracle – נוכל לסנן לפי התגיות הרלוונטיות וזהו. לא נצטרך להתחקות אחר הקטגוריות המובנות. ב stackoverflow הבינו את זה, ולקחו את זה עד הסוף, גם נוח וגם יעיל. אגב, גם אתרים כמו themarker הישראלי הרימו את הכפפה של תגיות.

ויש מערכת מוניטין עשירה (Voting and Reputation)

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

זה לא נגמר רק במוניטין של נקודות נטו. יש גם מערכת badgeים: מעין "תגים", או "עיטורי כבוד" לפעילות כזו או אחרת. שאלת שאלה שיש עליה יותר מ 10 הצבעות חיוביות – קיבלת את ה"עיטור" שנקרא Nice Question. שאלת שאלה שזכתה ל 2500 צפיות ומעלה – קיבלת את ה"עיטור" שנקרא Notable Question. ועוד ועוד.

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

יש שם המון המון (המון!) ידע. באמת המון

stackoverflow?

מתכנתי כל העולם התאחדו! אם ידע הוא כוח, הרי ש stackoverflow הוא לפחות הענק הירוק. קחו שאלה כמו Hidden Features of C# ותראו כמה ידע מזוקק אפשר לקבל מהרבה אנשים שכנראה מעולם לא הכרתם אישית.

יש היענות

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

ויש גם קצת הומור פה ושם

ה-gravatar שלי?

יום אחד, למשל, הוסיפו לינק מוזר של קשת בענן לכל שאלה (עם הכיתוב Cornify). כאילו בארבי בעצמה החליטה לתת חסות לאתר. לחיצה על הלינק המוזר גרמה להופעת תמונה בסגנון של קשת בענן, חד קרן, ירח וכד'. אחרי כמה לחיצות רצופות היתה תחושה שבארבי ובראץ חברו יחד (!) כדי להשתלט על הגיקים שמבקרים ב stackoverflow: המסך התמלא בתמונות קיטש מזעזעות. ואז, אחרי עוד לחיצה או שתיים, הופיעה הודעת "אחד באפריל" על המסך. חמוד, מצחיק, וגם, מה לעשות, קצת גיקי :wink:. כל זה היה באחד באפריל 2009. השנה (2010) המתיחה היתה יותר צנועה, ורק שינו את ה gravater של כולם להיות בסגנון של חד-קרן-קשת-בענן-לבבות.

נו, אז אם הכל כל כך טוב ויפה, אז מה יכול להיות רע?

אז נעבור לדברים… הלא כל כך טובים

זה תזזיתי

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

הניקוד יכול להטעות

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

stackoverflow?

שאלות קיטבג

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

אז מה נסגר?

טוב, זה עדיין נחשב להיות ה-אתר ה-מוביל של שאלות ותשובות בענייני תכנות. בעקבות ההצלחה שלו נפתחו אתרים דומים בתחומים אחרים (למשל, server-fault בתחום ה IT), או אתרים בהשראתו, מוצלחים יותר או פחות (למשל, iask הישראלי).

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

נתראה!

קטגוריות:תכנות תגיות:,
Quantcast