איך עובדים בגוגובוט?

אתמול (27.02) עשיתי הרצאה בשנקר במפגש של Ruby Underground.

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

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

העליתי את ההרצאה ל-Speaker Deck  כדי שכולם יוכלו להתרשם, גם אלו שלא נכחו בהרצאה.

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

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

Best practices לעבודה עם צד לקוח בריילס

הקדמה – למה כדאי לכם להמשיך לקרוא גם אם אתם לא מתכנתי רובי און ריילס

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

סדר וארגון מביאים ליעילות ולכן, לחסכון בכסף ובזמן

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

אם אתם מתכנתי Web סביר מאוד להניח שיש לכם עשרות, אם לא מאות, ואם לא אלפים שורות קוד לכתוב ב-JavaScript וב-Css. סביר גם להניח שיש לכם מספר גדול מאוד של קבצים לעבוד איתם.

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

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

ארגון CSS צריך להיות מבוסס על Controllers

התמונה למטה, מראה רשימת Controllers טיפוסית בפרויקט תו"כ פיתוח.

רשימת Controller בפרויקט

רשימת Controller בפרויקט

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

אבל ראשית, יש לי כמובן קובץ Css ראשי אחד, שבו יש את כול החוקים שהם משותפים לכול האתר, לקובץ ה-Css הזה אני בד"כ קורא main.css (מיד נראה איך הכול מאורגן בתיקיות כמובן).

להלן ספריית ה-Css שלי בהתבססות על הקונטרולרים. שימו לב לדבר אחד, אני משתמש ב-Compass וב-Scss בשביל לכתוב את הקוד, ולכן הקבצים הם לא רגילים. כמו כן, כאן הקובץ הראשי שלי נקרא dashboard.css מכיוון שהפרויקט הוא אפליקציה ולא אתר.

קבצי Css

קבצי Css

שימו לב לדבר נוסף, הקלאס הראשי בתוך הקובץ הוא בשם הקובץ שלי ולכן בשם הלוגי של המקום שבו הוא נמצא.

שימוש בקלאס ראשי לארגון התוכן הפנימי של הדף

אם נסתכל על הקובץ Layout הראשי שלי, אנחנו יכולים לראות איפה אני משתמש בקלאס הזה

	<body class="<%= controller_classes.join(' ') %>">
		<div class="wrapper">
			<div class="header <%= yield(:header_class)%>">
	    		<%= yield(:upper_links) %>
		   		<%= yield(:dashboard_header) %>
				<%= yield(:top_button) %>
		    </div><!--/header-->
		    <div class="content">
				<%= yield %>
		    </div><!--/content-->
			<%= yield(:new_issue) %>
		</div><!--/wrapper-->
		<div class="footer">
			<%= render :partial => "shared/dashboard/footer" %>
		</div><!-- .footer -->

		<%- flash.each do |name, msg| -%>
			<div id="flash_message" class="flash <%=name%>">
				<strong>
					<%= msg %>
				</strong>
			</div>
      	<%- end -%>
	</body>

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

את ה- controller_classes אני קובע דרך ה- application_helper בצורה הבאה:

module ApplicationHelper
  def controller_classes
    [controller.controller_name]
  end
end

למה זה מערך?

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

עכשיו, אתם בטח תוהים איך אני לוקח את כול קבצי ה-Css האלה. העוקצנים שבינכם בטח כבר חשבו למה אני טוען Css בכול עמוד בנפרד במקום קובץ אחד ויחיד שהוא Minified. ובכן, לא כך המצב. אני אכן טוען את כול הקבצים לתוך קובץ אחד ויחיד (ב-Production) שהוא Minified וגם gzipped.

אני מבצע את הדבר הזה דרך Gem שנקרא Jammit שעושה בדיוק את זה, הGem משתמש בקובץ Yaml בשביל לאחד את הקבצים, היופי ב-Gem הזה שבזמן פיתוח הוא נותן לי את כול הקבצים בצורת המקורית (אחרי ש-Compass עשה מהם Css רגיל), ובזמן Production מוגש קובץ אחד ויחיד.

אפשרות נוספת היא לעשות שימוש ב-Import של Sass, במקרה הזה הקבצים צריכים להתחיל עם Underscore ופשוט לייבא את כולם לתוך קובץ יחיד שנקרא לו application.scss או כל שם אחר שבא לכם.

דוגמא ל-Jammit Asset file

stylesheets:
  base:
    - public/stylesheets/base/all.css
    - public/stylesheets/base/form.css

ארגון קבצי JavaScript

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

הקובץ הראשון הוא הקובץ הכללי שכולל בתוכו קוד כללי לכול העמודים (אם יש כזה), כולל את ה-Framework לבחירה (Mootools כמובן) ועוד תוספים כאלה ואחרים.

להלן דוגמא:

javascripts:
  base:
    - public/javascripts/base/mootools-core-1.3.js
    - public/javascripts/base/mootools-more.js
    - public/javascripts/base/rails.js
  dashboard_base:
      - public/javascripts/base/flash_hanler.js
      - public/javascripts/base/scroller_bar.js
      - public/javascripts/base/behavior.js
      - public/javascripts/base/custom_select.js
      - public/javascripts/base/custom_checkbox.js
      - public/javascripts/base/paging_list.js
      - public/javascripts/base/new_project.js
      - public/javascripts/base/project.js
      - public/javascripts/modules/project/newIssue.js
      - public/javascripts/base/main.js

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

בנוסף, לכול אחד מה-controllers יש קובץ משלו. למשל home.js, user.js ועוד.

את הקובץ הזה אני טוען בתחתית העמוד מיד אחרי קובץ הBase בצורה הבאה:

<%= javascript_include_tag "/javascripts/views/#{controller.controller_name}.js" %>

האתר שלי הרבה יותר עשיר בJavaScript, רעיון לשיפור?

בהחלט, כרגע אנחנו עובדים ב-controller scope, אפשר בהחלט לעבוד ב-view scope ולייצר קבצים (גם Js וגם Css) לכול אחד מה-Views שלכם ול-Partials.

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

הערת שוליים קטנה או => נראה לכם באמת שאני עובד ככה…

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

מי אמר Rails Custom Generators?

בדיוק בשביל זה יש לי Custom Generator ובמקום ליצור Controller דרך הקומנד ליין הרגיל, אני יוצר אותו דרך Custom Command שבעצם עושה את כול הדברים בשבילי.

  • יצירת Controller
  • יצירת קובץ Scss
  • הוספה של הקובץ לסדר הMinify (פרמטר אופציונאלי האם הוא בייס או לא)
  • הוספה של קובץ Js
  • הוספה של הקובץ לסדר הMinify (פרמטר אופציונאלי האם הוא בייס או לא)

לסיכום

אשמח לשמוע תגובות, הערות, הארות (אני פתוח לכול ביקורת חיובית :-) )

לייקים בפייסבוק וגם תגובות דרך הפייסבוק יתקבלו גם הן בברכה.

בהצלחה!

סקרינקאסט Ajax עם מוטולס ורובי און ריילס (חלק 1)

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

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

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

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

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

גם לייקים יתקבלו בברכה

Ajax on rails using Mootools [HD] from Avi Tzurel on Vimeo.

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

A/B Testing או איך ריילס תעזור לכם להרוויח יותר כסף מהאתר שלכם?

בהתקדמותכם הלאה בפוסט הזה, אתם מצהירים מספר דברים:

  1. יש לכם אתר אינטרנט ואתם רוצים להרוויח ממנו יותר כסף
  2. אתם לא חאפרים ולא בונים אתר אצל חאפרים

טוב, נמשיך?

מהו A/B Testing?

A/B Testing זו בעצם דרך לבדוק את הוריאציות השונות של האפליקציה שלכם ואיך הוריאציות האלה מתפקדות בעיני הגולשים שלכם. מה הייתם אומרים אם הייתי אומר לכם ששינוי כותרת יכול להביא לעלייה של 12% במכירות השבועיות שלכם? מה הייתם אומרים אם הייתי אומר לכם שפונט קטן יותר ושינוי של פסיק בפסקה מסוימת יכול להביא לעליה של 5% נוספים?

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

בעולם התחרותי של היום, עולם שבו יש מוצרים באינטרנט, SAAS) Software As A Service) במחירים שונים ובמקומות שונים יש המון חשיבות למסרים ויזואליים, מיקומם של המסרים האלה ועוד. לפעמים מדובר בדברים בולטים לעין, כאלה שאם תקחו כותב תוכן טוב או מעצב גרפי טוב תצליחו לשפר באחוז ניכר את המכירות שלכם ואת האינטראקציה של גולשים עם האתר. אבל, לא על זה אני מדבר – אני מדבר על השיפורים הקטנים, מיקום של כותרת, מה יהיה כתוב בה וכדומה.

דוגמא ל-A/B Testing במוצר שמכניס מיליונים ויכול להכניס יותר

החבר'ה מ37Signals כתבו פוסט מעלף/מאלף על ניסוי שהם ערכו. הניסוי שהם ערכו דיבר על כותרת ותת כותרת בדף הרשמה לחשבון. זה הכול.
למשל: וריאציה ראשונה
hrhq-signuphead-30day60sec

והוריאציה השנייה
hrhq-signuphead-start30trial

אני יודע, מדגדג לכם על הלשון להגיד שזה לא באמת משנה נכון?

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

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

אבי, אבי, אבי שכנעת אותי, איך אני ממשיך מכאן?

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

אם יש לכם אפליקצית .net או php או אפילו python אפשר כמובן לעשות את זה באמצעות Google Web Optimizer. זה לא סקסי כמו ריילס ולא מובנה בתוך האפליקציה כחלק בלתי נפרד אבל זה כמובן ולידי לגמרי וניתן לביצוע. אפשר גם לבצע את זה באמצעות Google Analytics אבל שוב, זה לא מובנה בתוך האפליקציה ותצטרכו לכתוב הרבה יותר קוד בשביל זה.

אז עם ריילס זה יותר קל?

הכול עם ריילס יותר קל (ונעים ונחמד וכיף :-) )

מה נעשה עכשיו?

עכשיו אנחנו ניקח אפליקציה מאפס ונבנה אותה ל-A/B Testing. אנחנו ניצור טופס הרשמה, בטופס הזה נשים פסקה של טקסט, ונבדוק איך משפיעה הפסקה הזו על כמות האנשים שנכנסים ובאמת נרשמים בסוף.

נתחיל בלייצר אפליקציה שנקרא לה ab_testing.

rails ab_testing

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

הנה הView שלנו:

<% title "Free Sign up" %>

<p>
	Register below to discover the awsomeness of our website
</p>

<p>Already have an account? <%= link_to "Log in", login_path %>.</p>

<% form_for @user do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </p>
  <p>
    <%= f.label :email, "Email Address" %><br />
    <%= f.text_field :email %>
  </p>
  <p>
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </p>
  <p>
    <%= f.label :password_confirmation, "Confirm Password" %><br />
    <%= f.password_field :password_confirmation %>
  </p>
  <p><%= f.submit "Sign up" %></p>
<% end %>

וכך זה נראה בדפדפן:
Registration form

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

לבחור את הכלי הנכון

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

הכלי שאנחנו נדבר עליו ונכסה אותו יהיה A/Bingo, זהו בעצם Plugin לרובי און ריילס שמבצע בדיוק את מה שאנחנו צריכים.

התקנה

כול מה שצריך בשביל להתקין את הפלאגין זה כרגיל בריילס, להריץ שורת פקודה:

script/plugin install git://git.bingocardcreator.com/abingo.git

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

script/generate abingo_migration

זה ה-Migration שנוצר לנו כתוצאה מהפקודה:

class AbingoMigration10< ActiveRecord::Migration
  def self.up
    create_table "experiments", :force => true do |t|
      t.string "test_name"
      t.string "status"
      t.timestamps
    end

    add_index "experiments", "test_name"
    #add_index "experiments", "created_on"

    create_table "alternatives", :force => true do |t|
      t.integer :experiment_id
      t.string :content
      t.string :lookup, :limit => 32
      t.integer :weight, :default => 1
      t.integer :participants, :default => 0
      t.integer :conversions, :default => 0
    end

    add_index "alternatives", "experiment_id"
    add_index "alternatives", "lookup"
  end

  def self.down
    drop_table :experiments
    drop_table :alternatives
  end
end

לאחר שיש לנו Migration, נבצע Migrate לבסיס הנתונים שלנו (זה אומר בעצם לייצר את כול הטבלאות, שדות וקשרים לפי הMigration שייצרנו לפני שנייה):

rake db:migrate

הגענו לבדיקה הראשונה, יש!

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

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


<% title "Free Sign up" %>

<% if ab_test "signup_paragraph "%>
<p>
 Register below to discover the awsomeness of our website
</p>
<%else%>
<p>
 This is the alternative content
</p>
<%end%>

<p>Already have an account? <%= link_to "Log in", login_path %>.</p>

<% form_for @user do |f| %>
 <%= f.error_messages %>
 <p>
 <%= f.label :username %><br />
 <%= f.text_field :username %>
 </p>
 <p>
 <%= f.label :email, "Email Address" %><br />
 <%= f.text_field :email %>
 </p>
 <p>
 <%= f.label :password %><br />
 <%= f.password_field :password %>
 </p>
 <p>
 <%= f.label :password_confirmation, "Confirm Password" %><br />
 <%= f.password_field :password_confirmation %>
 </p>
 <p><%= f.submit "Sign up" %></p>
<% end %>

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

את השמירה אנחנו נעשה ב-Controller מיד אחרי שמירת המשתמש:

class UsersController < ApplicationController
 def new
 @user = User.new
 end

 def create
 @user = User.new(params[:user])
 if @user.save
 bingo! "signup_paragraph"
 session[:user_id] = @user.id
 flash[:notice] = "You are registered and signed up!"
 redirect_to root_url
 else
 render :action => 'new'
 end
 end
end

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

bingo! "signup_paragraph"

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

צפייה בתוצאות המבחן

אנחנו נייצר Controller כדי לצפות בתוצאות:
ֿ

script/generate controller abingo_dashboard

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

כך נראה ה-Controller שלנו:

class AbingoDashboardController < ApplicationController
  include Abingo::Controller::Dashboard
end

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

  map.abingo_stats "/abingo_stats/:action/:id", :controller=> :abingo_dashboard

עכשיו שיש לנו route מוגדר, בואו ונצפה בתוצאות (אם הserver שלכם פועל, צריך לאתחל אותו):
Abingo Stats

סיכום

נגענו בקצה הקרחון של A/B Testing – גם מבחינת הקונספט וגם מבחינת איך לבצע את זה ב-Ruby on Rails. כמובן שיש עוד הרבה אופציות לבחון, ויש אפשרות לאחד מבחנים תחת כותרת מסוימת וכדומה.
יש דרכים לזהות משתמשים רשומים, לעשות Random של האם יוצג או לא ועוד עשרות שיטות שאפשר לבחון כול מקרה לגופו. האפשרויות (כרגיל) הן בלתי מוגבלות.

ריבוי מילים בפלקס (או… מה שמובן מאליו למתכנתי רובי און ריילס)

SingularPluralSort.pdf.00

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

בעצם זה נשכח, עד השבוע.

השבוע קיבלתי באג במערכת של לקוח שאומר שכאשר הוא בוחר מודול למחיקה הוא רואה (are you sure you want to delete the selected module) והוא רואה את אותה ההודעה בדיוק כאשר הוא בוחר מספר מודולים.

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

 

var module:String = "module";

			if(list.selectedItems.length > 1)
				module = "modules";

			Alert.show(StringUtil.substitute("are you sure you want to delete the selected {0}", module);

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

מה עשיתי?
1. חיפשתי בגוגל ומצאתי שמישהו עשה porting לקוד של מחלקת Pluralization מריילס ועשה אותה בas3
2. שכתבתי את הקוד וחשפתי החוצה פעולה אחת בלבד

הנה הקוד המלא (לשימושכם החופשי):

		public function PluralizationHelper()
		{
		}

		static:{
			PluralizationHelper.initPluralization();
		}

		private static var pluralWords : Array =
			[
				[/$/, 's'],
				[/s$/i, 's'],
				[/(ax|test)is$/i, '$1es'],
				[/(octop|vir)us$/i, '$1i'],
				[/(alias|status)$/i, '$1es'],
				[/(bu)s$/i, '$1ses'],
				[/(buffal|tomat)o$/i, '$1oes'],
				[/([ti])um$/i, '$1a'],
				[/sis$/i, 'ses'],
				[/(?:([^f])fe|([lr])f)$/i, '$1$2ves'],
				[/(hive)$/i, '$1s'],
				[/([^aeiouy]|qu)y$/i, '$1ies'],
				[/(x|ch|ss|sh)$/i, '$1es'],
				[/(matr|vert|ind)ix|ex$/i, '$1ices'],
				[/([m|l])ouse$/i, '$1ice'],
				[/^(ox)$/i, '$1en'],
				[/(quiz)$/i, '$1zes']
			];

		private static var singularWords : Array =
			[
				[/s$/i, ''],
				[/(n)ews$/i, '$1ews'],
				[/([ti])a$/i, '$1um'],
				[/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '$1$2sis'],
				[/(^analy)ses$/i, '$1sis'],
				[/([^f])ves$/i, '$1fe'],
				[/(hive)s$/i, '$1'],
				[/(tive)s$/i, '$1'],
				[/([lr])ves$/i, '$1f'],
				[/([^aeiouy]|qu)ies$/i, '$1y'],
				[/(s)eries$/i, '$1eries'],
				[/(m)ovies$/i, '$1ovie'],
				[/(x|ch|ss|sh)es$/i, '$1'],
				[/([m|l])ice$/i, '$1ouse'],
				[/(bus)es$/i, '$1'],
				[/(o)es$/i, '$1'],
				[/(shoe)s$/i, '$1'],
				[/(cris|ax|test)es$/i, '$1is'],
				[/(octop|vir)i$/i, '$1us'],
				[/(alias|status)es$/i, '$1'],
				[/^(ox)en/i, '$1'],
				[/(vert|ind)ices$/i, '$1ex'],
				[/(matr)ices$/i, '$1ix'],
				[/(quiz)zes$/i, '$1']
			];

		private static var irregularWords : Array =
			[
				['person', 'people'],
				['man', 'men'],
				['child', 'children'],
				['sex', 'sexes'],
				['move', 'moves']
			];

		private static var uncountableWords : Array =
			[
				'equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'
			];

		private static function makePlural (singular : String) : String
		{
			if (uncountableWords.indexOf(singular) != -1)
				return singular;

			var plural : String = new String();
			for each (var item : Array in pluralWords)
			{
				var p : String = singular.replace(item[0], item[1]);
				if (p != singular)
					plural = p;
			}
			return plural;
		}

		private static function makeSingular (plural : String) : String
		{

			if (uncountableWords.indexOf(plural) != -1)
				return plural;

			var singular : String = new String();
			for each (var item : Array in singularWords)
			{
				var s : String = plural.replace(item[0], item[1]);
				if (s != plural)
					singular = s;
			}
			if(singular == "")
				return plural
			else
				return singular;
		}

		[Bindable(event="pluralizationChangedEvent")]
		public static function pluiralize(count:int, word:String):String
		{
			if(count == 1)
				return makeSingular( word );

			return makePlural( word );
		}

		static private function initPluralization() : void
		{
			for each (var arr : Array in irregularWords)
			{
				pluralWords[pluralWords.length] = [arr[0], arr[1]];
				singularWords[singularWords.length] = [arr[1], arr[0]];
			}
		}
	}

איך מפעילים את הקוד מתוך האפליקציה? ובכן, כך:

PluralizationHelper.pluiralize(count, 'custom field')

הקוד הזה הוא חלק מספרייה שאני משחרר שהיא אוסף Helpers כלליים לאפליקציות פלקס.
את קוד המקור של כול הספרייה (שכרגע מכילה רק את הקלאס הזה אבל תתעדכן בהמשך) אפשר למצוא בgit בקישור הבא
http://github.com/KensoDev/Flex-Generic-Helpers

תהנו!

אם יש שאלות, תרגישו חופשי

הטעויות הכי נפוצות (וגדולות) בפיתוח אפליקציות פלקס

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

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

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

1. שימוש בflex כדי לבנות אתרי אינטרנט או אפליקציות מדור ישן

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

אני זוכר, בתור ילד צעיר למדתי קראטה, with great power comes great responsibility, אמצו את זה כאשר אתם ניגשים לפתח אפליקציה. אם אין לכם אפליקציה שדורשת ויזואליות מסוימת, אנימציות, עבודה עם datasets גדולים וכו', עדיף שתשתמשו בטכנולוגיות אחרות כמו html, css ואפילו פלאש.

Flash this not that

Flash this not that

אפשר לראות פוסט באנגלית על זה כאן

מקור התמונה The Flash Blog

2. האטת האפליקציה עד כדי זחילה על ידי שימוש ביותר מדי containers

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

השקף הזה מדבר על ריבוי containers. הרבה אנשים שמגיעים מhtml לתוך עולם הפלקס (וזה קורה המון) לא מבינים שcontainer  בפלקס הוא component בפני עצמו שיש מאחוריו עשרות (לעיתים מאות) חישובים של גודל, מיקום, מיקום ילדים, גודל ילדים. ועוד יותר גרוע מזה, ולידציות וinvalidations.

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

אפשר לראות כאן צילום מסך של תחקיר שעשו חברים בקהילה מאתר Inside RIA

צריכת זכרון כאשר יש יותר מדי Containers

צריכת זכרון כאשר יש יותר מדי Containers

ניקח דוגמה שפורסמה בזמנו בYahoo Developer Network (את המאמר המקורי אפשר למצוא כאן, אבל אני מתמצת את עיקרו שרלוונטי לנושא)

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

יותר מדי container באפליקצית flex

יותר מדי container באפליקצית flex

הדרך הראשונה

והקוד:

 <Application layout="absolute">

     <HBox width="100%">

         <Button label="Left"/>

         <Spacer width="100%"/>

         <Button label="Left"/>

     </HBox>

 </Application>
יותר מדי container באפליקצית flex

יותר מדי container באפליקצית flex

שימו לב שיש לנו כאן מספר containers, רובם מיותרים, סבב התיקונים הראשון ועכשיו האפליקציה שלנו נראית כך

הקוד שלנו נראה כך:

 <Application layout="horizontal">

     <Button label="Left"/>

     <Spacer width="100%"/>

     <Button label="Left"/>

 </Application>

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

מספר container נכון באפליקצית flex

מספר container נכון באפליקצית flex

הטיוב האחרון שלנו נראה כך:

והקוד נראה כך:

 <Application layout="absolute">

     <Button label="Left" left="5"/>

     <Button label="Left" right="5"/>

 </Application>

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

3. שימוש בXML

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

XML לא רק לוקח יותר זמן לפרסר אלא לוקח יותר זמן להעביר על הwire ויכול לעלות המון כסף של BandWidth.

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

את האפליקציה הזו אפשר למצוא כאן

דוגמאות לסוגי העברת מידע בין Client לServer

דוגמאות לסוגי העברת מידע בין Client לServer

4. שבירת מוסכמות והרגלי גלישה באינטרנט

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

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

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

5. שימוש מוגזם באנימציות ומעברי עמודים

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

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

יודעים מה, כבר עשיתם את זה, תאפשרו למשתמש לכתוב את זה.

כנ"ל עם מוזיקה או Audio Effects.

6. לא לפתח סביבת פיתוח תומכת Craftsmanship או Agile או לא לפתח EcoSystem בכלל

זה נכון לכול טכנולוגיה, בין אם זה flex או אפילו Ruby on rails. לגשת ישירות לפיתוח מבלי להתחשב ולא להנהיג שיטות Tdd/Bdd, Unit Testing, Code Coverage וכו' זה פשוט לא אחראי, זה לא אחראי במיוחד באפליקציות פלקס מכיוון שכשלא עושים את זה, צריכים לבדוק את הכול דרך הUI, לחכות לקומפיילר שירנדר את האפליקציה בכול פעם, זה בזבוז זמן ומשאבים אדירים.

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

הנה כמה:

FlexUnit

FlexMonkey

תפתחו שיטות עבודה איכותיות שמעודדות בדיקות, מעודדות Craftsmanship, מעודדות Clean Code ואחריות מפתחים.

7. לא לעשות שימוש בBuild Machine ובContinuous Integration

היות ואפליקציות Flex עוברות קומפליציה, כלומר מקוד מקור לאפליקציה סגורה, קובץ SWF שיטת הpublish צריכה להיות כזו

תהליך פיתוח ומעבר נכון לProduction

תהליך פיתוח ומעבר נכון לProduction

מה זה אומר בעצם?

זה אומר במפתח עושה Commit לקוד שלו, לתוך מכונה יעודית, במקרה הזה עדיף לעשות שימוש בGIT כי אז הוא יכול לעשות local commits וpush רק כשהוא בטוח שיש אצלו הכול.
המפתח כמובן בודק את הקוד שלו, כך שקוד שנכנס למכונה הוא קוד שעבר בדיקות.
על המכונה הקוד עובד בדיקות נוספות, כדי לבדוק Integration של כול המפתחים, במידה וכול הבדיקות עוברות, הקוד מתקמפל לאפליקציה ועולה לאוויר (אם צריך)

יעיל נכון?

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

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

כרגיל, אשמח לשמוע את דעתכם.