כמה מסקנות מהעסקת פרילנסרים במזרח הרחוק בפיתוח תוכנה / אתרים (Odesk & Elance)

האם הפרילנסרים במזרח יחליפו אותנו ? לא בטוח...
האם הפרילנסרים במזרח יחליפו אותנו ? לא בטוח…
Photo by aq-photography.com

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

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

אני אפרט כאן את המסקנות האישיות שלי

בתקווה שיועילו לאנשים אחרים.

  1. בדברים פשוטים (בעיני, כמפתח) כמו חיתוך PSD ל- HTML/CSS

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

אומנם בחלק מהמקרים, הייתי צריך לתת לבנאדם הרבה מאוד הנחיות

והערות בלי סוף, עד שהגענו לתוצאה סבירה

אבל, המחיר השתלם למרות זאת.

אני אתן דוגמאות :

חיתוך 12 דפים ארוכים יחסית, ל-html/css רספונסיבי, מבוסס bootstrap 3 בעברית

עלה לי בהודו 218$, ובארץ דבר כזה היה עולה לפחות 3000 ש"ח להערכתי.

החיסכון (1$=4ש"ח) הוא כ : 2,128 ש"ח

עלות חיסכון : היו פה יותר מ-10 סבבים של תיקונים,

כולם בכתיבה מייגעת  באנגלית.

 

דוגמא נוספת : חיתוך דף בודד – 28$ (בנגלדש, Elance)

דוגמא נוספת : חיתוך דף בודד , רספונסיבי – 60$ (הודו)

גרפיקה + חיתוך , 3 דפים שונים – 48$ (הודו, 3 סקיצות כמקובל).

 

  1. בדברים בהם היה צורך למצוא מתכנת , ולא רק עיצוב או חיתוך – יצאתי ב-"שן ועין"

או ברווח נמוך.

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

קיבלתי עבודה מאוד מאוד גרועה, שהייתי צריך לשכתב את כל הקוד בעצמי , אל תוך הלילה.

ובנוסף שילמתי לו 550$.

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

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

הוא טען שהוא מוכן לפתור את זה עד הסוף… עניתי לו שזה לא רלוונטי

כי הקוד כתוב מתחת לכל רמה סבירה.

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

 

מקרה אחר – למדתי את הלקח, שילמתי 25$ לבחור רוסי, שעשה עבודה יפה מאוד.

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

במקרה זה חסכתי זמן, אך לא כסף, כי בישראל יכולתי למצוא מישהו במחיר זה

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

 

  1. מקרים שאינם קשורים לפיתוח –

היו לי כמה מקרים נוספים, שבהם ניסיתי

חלקם רק לקבלת עצה מקצועית "באופן מיידי"

שלא מצאתי לה מענה בגוגל.

וחלקם – לפרויקטים של אדמיניסטרציה / עבודה שחורה.

במקרים של העצות – שילמתי מחיר סביר (כ-30$ לשעה, בדרך כלל לא יותר משעה-שעתיים והעניין נפתר)

באמת היה ידע בצד השני, והייתה מוכנות לעזור,

ובמקרים מסויימים גם היתה הוגנות (קרו לי כמה מקרים

שבהם האנשים ששכרתי לא ידעו לפתור את הבעיה

ולמרות שלפי הכללים, היו אמורים לחייב אותי על הזמן שהשקיעו – לא חויבתי בפועל).

במקרים אחרים – שלחתי פרויקטים מאוד גדולים של אדמיניסטרציה לחו"ל.

העברית – מהווה בעיה, שלא תמיד קל להתגבר עליה.

כלומר לפעמים הייתי צריך לכתוב כל כך הרבה הנחיות באנגלית

שזמן הצאט / הכתיבה – היה סוג של הפסד (כי לישראלי, הייתי פשוט מסיים ב-5 דקות הסבר טלפוני).

בנוסף , היה מקרה שבו פירסמתי פרויקט גדול לפי שעות

ובסופו של דבר הפרויקט הסתיים הרבה יותר מהר מהצפוי.

והבנאדם בצד השני (הודו) , פתח dispute (תביעה נגדי) ב-elance

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

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

כי לא ידעתי מה הם יחליטו בסופו של דבר.

 

מסקנות :

  1. דברים פשוטים – אפשר לשלוח בכיף.

  2. יש לקחת בחשבון שזמן ההתנהלות מולם כפול ומכופל לפחות פי 6 אם לא יותר לעומת מישהו בעברית.

  3. בדברים מורכבים – היתרון שאני מצאתי, הוא מהירות הביצוע

אבל אין חיסכון מבחינת עלות – המחיר  דומה מאוד לישראלי.

  1. חשוב להשתמש בכל האמצעים שמעמידים לרשותכם האתרים, כדי לבטח את עצמכם.

כי טעויות קורות, ואף אחד לא רוצה לשלם על טעות.

אז אם הפרויקט לפי שעות – תדרשו עבודה עם time tracker שמתעד את צילומי המסך שלהם.

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

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

  1. בפרויקטים גדולים, יש לקחת בחשבון עלויות מימון

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

  1. ומסקנה אחרונה – מעודדת – לפחות כרגע, אין מצב לדעתי

שההודים יחליפו אותנו. 🙂

 

בהצלחה!

מצרף סרטון שנתקלתי בו, ועזר לי בזמנו.

פונקציה לבדיקת האם מחרוזת מתחילה באנגלית – VBA (Access)

לפעמים צריך ליישר אוטומטית

שדה ב-VBA

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

במידה וכן – ניישר אותו בהתאם.

Public Function StringStratInEnglish(str As String) As Boolean
 Dim first_char As String
 On Error Resume Next
 
 StringStratInEnglish = False
 
 If Len(str) = 0 Then GoTo ExitHere
 first_char = Mid(str, 1, 1)
 
 If (Asc(first_char) >= 65 And Asc(first_char) <= 90) Or _
 (Asc(first_char) >= 97 And Asc(first_char) <= 122) Then
 
 StringStratInEnglish = True
 End If
 
ExitHere:
 Exit Function
End Function

הצגת מונה רשומות בתוך טופס Access

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

ולא להשתמש במונה המובנה של אקסס

 

השיטה הכי פשוטה היא לעשות תיבת טקסט ובתוכה

=" Record  " & [CurrentRecord] & " From  " & Count(*)


אבל  הבעיה היא שהשיטה הזו מחזיקה מעמד רק עד שפותחים רשומה חדשה

ברגע שפותחים רשומה חדשה – מקבלים דברים מוזרים כמו "רשומה 7 מתוך 6" וכדומה.

 

אז שיטה יותר טובה היא לשים באירוע של OnCurrent ( בנוכחי) קוד שבמידה והטופס נמצא על רשומה קימת – נשתמש ב-DCount כדי למנות את מספר הרשומות הקיים.

 

 If Me.NewRecord Then
 Me.txtCounter.Value = "New Record ( " & DCount("[PolicyID]", "tblPolicies", "[linkID]='" & Nz(Me.linkID, "") & "'") & " Exists Records "
 
 Else
 Me.txtCounter.Value = "Record " & Me.CurrentRecord & " Of " & DCount("[PolicyID]", "tblPolicies", "[linkID]='" & Nz(Me.linkID, "") & "'")
 End If

כמובן שתשנו את ה-Dcount לטבלה הרלוונטית עבורכם.

 

מקור להרחבה ושיטות נוספות : http://www.fontstuff.com/mailbag/qaccess04.htm

 

שימוש ב-Web Control המובנה של Access (דפדפן) ב-VBA

בגירסאות האחרונות של Microsoft Access יש Web Control מובנה

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

ב-VBA הגישה היא כזו :

.YourWebControl.ControlSource = "=" & Chr(34) & file_name & Chr(34)

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

YourWebControl.ControlSource = "=""www.google.co.il""

פתיחת קובץ דרך שורת הפקודה של Windows ב-VBA ( באמצעות WSscript)

בדרך כלל אם רוצים להריץ פקודה ב-Shell, יש לשם כך את הפקודה Shell

אלא מה – זוהי פקודה מאוד נכה, שלא תמיד עובדת.

נניח – אם תנסו להריץ קובץ lnk, זה לא יעבוד, כנ"ל לגבי קבצי pdf, וכדומה.

ולכן,
משתמשים ב-wsScript

הערה – אם הפקודה מכילה ארגומנטים או רווחים, תכפילו את המרכאות ב-4, או לחלופין יש להשתמש ב-chr(34)

Dim objshell As Object

Set objshell = CreateObject("WScript.Shell")
    
objshell.Run "C:YouPathYourFile.lnk"

יצירת קיצור דרך (קובץ lnk) באמצעות VBA

הקוד הבא שוכלל על ידי מעט

הוא יוצר קובץ קיצור דרך
FileName – שם קובץ מלא, כולל תיקיה של קיצור הדרך שיווצר
Target – הקובץ שנרצה לפתוח
YourArgumentFile – אם יש ארגומנטים אחרי שם הקובץ, הם חייבים לבא כאן
working directory + descrption – אפשרי למלא – ואז הם יהיו מאפיינים בתור קיצור הדרך

Sub CreateAShortcut(FileName As String, Target As String, YourArgumentFile As String, Optional WorkingDirectory As String, _
                        Optional WindowStyle As Integer = vbNormalFocus, _
                        Optional Description As String)


With CreateObject("WScript.Shell").CreateShortcut(FileName)
    .TargetPath = Target
    .Arguments = YourArgumentFile 
    .WindowStyle = WindowStyle
    .Description = Description
    .WorkingDirectory = WorkingDirectory
    .Save
End With
End Sub

מקור
http://windowssecrets.com/forums/showthread.php/62044-Creating-File-Shortcuts-(VBA-Office-XP)

העברת ADO RECORDSET אל מערך ב-VBA

קורה לפעמים שעדיף לרוץ על מערך בזיכרון מאשר על רקורדסט – זה הרבה הרבה יותר מהיר

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


Public Function RecordsetToArray(ByRef rs As Object) As Variant
    Dim tmp As Variant, cols_num As Integer, K As Integer, rows_num As Integer, J As Integer
    
    If Not (rs.EOF And rs.BOF) Then
    
        rs.MoveFirst
        cols_num = rs.fields.Count
        rows_num = rs.RecordCount
        
        ReDim tmp(rows_num, cols_num)
            
            J = 0
            Do While Not rs.EOF
                For K = 0 To cols_num - 1
                      tmp(J, K) = Nz(rs.fields(K).Value, "")
                Next K
                J = J + 1
                rs.MoveNext
            Loop
    
    Else
        tmp = Array("")
    
    End If
    
    RecordsetToArray = tmp
    
End Function




המתנה ב-ACCESS VBA באמצעות WINDOWS API

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

להלן קוד.

#If VBA7 Then
    Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
#Else
    Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If


Sub SleepVBA(ms As Integer)
'ms = milisecondes
Sleep ms
End Sub

פונקצית VBA לחישוב ערכים באמצעות אקסל חיצוני (למשל מאקסס או מוורד)

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

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

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

מבנה המערכים –
שתי המערכים באותו מבנה – דו מימדי, כאשר תיאור העמודות נשמר ב
ENUM = E_XL_DataAry

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

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

Public Enum E_XL_DataAry
    
    SheetNameCol = 0
    CellAddressCol = 1
    ValueCol = 2

End Enum


Public Function CalculateByExcel(file_name As String, DataToPutAry As Variant, DataToGetAry As Variant) As Variant
    
    Dim xl As Object, wb As Object, ws As Object, I As Integer
    'open the xl
    Set xl = CreateObject("Excel.Application")
    xl.Visible = False
    Set wb = xl.Workbooks.Open(file_name, 2, True)
    
    If (Not IsArrayAllocated(DataToPutAry)) Or (Not IsArrayAllocated(DataToGetAry)) Then GoTo Err_Handel
    
    'Put Data into cells
    For I = LBound(DataToPutAry) To UBound(DataToPutAry)
            wb.Worksheets(DataToPutAry(I, E_XL_DataAry.SheetNameCol)).range(DataToPutAry(I, E_XL_DataAry.CellAddressCol)).Value = DataToPutAry(I, E_XL_DataAry.ValueCol)
    Next I
    
    For I = LBound(DataToGetAry) To UBound(DataToGetAry)
            DataToGetAry(I, E_XL_DataAry.ValueCol) = wb.Worksheets(DataToGetAry(I, E_XL_DataAry.SheetNameCol)).range(DataToGetAry(I, E_XL_DataAry.CellAddressCol)).Value
    Next I
     
    CalculateByExcel = DataToGetAry
     
    wb.Close False ' close the source workbook without saving any changes
    Set wb = Nothing
    xl.Quit
    Set xl = Nothing

Exit_Func:
    Exit Function


Err_Handel:
    CalculateByExcel = False
    Resume Exit_Func
    
End Function

דוגמא לשימוש פשוט – כאשר מוחזר רק תא אחד בודד


Sub TryIt()
Dim x As Variant, a As Variant, b As Variant
ReDim a(0, 3)
a(0, 0) = "Sheet1"
a(0, 1) = "A1"
a(0, 2) = "1"
ReDim b(0, 3)
b(0, 0) = "Sheet1"
b(0, 1) = "A2"


x = CalculateByExcel("YourXLFile.xlsx", a, b)
Debug.Print x(0, 2)

End Sub

ודוגמא לשימוש מורכב יותר, כאשר מוחזר טווח של 2 תאים לדוגמא


Dim x As Variant, a As Variant, b As Variant
ReDim a(0, 3)
a(0, 0) = "Sheet1"
a(0, 1) = "A1"
a(0, 2) = "1"
ReDim b(0, 3)
b(0, 0) = "Sheet1"
b(0, 1) = "A2:A3"


x = CalculateByExcel("YourXLFile.xlsx", a, b)
Debug.Print x(0, 2)(1,1)

End Sub

בהצלחה!

איך לגרום לשורה במבט טבלאי של דוח Access להסתיר את עצמה ?

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

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

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

בהצלחה!

Credit : Yochai