בניית פרוייקט עם Spring MVC, Hibernate, Maven

בפוסט זה אדגים כיצד לבנות פרוייקט end-to-end בטכנולוגיות: Spring MVC, Hibernate קומפילציה באמצעות Maven והרצה כWAR על Tomcat או כל שרת אפליקציות ג'אווה אחר. הפרוייקט יכלול בסיס נתונים MySQL עם טבלה השומרת פרטי לקוחות, דף JSP שמציג נתונים מהטבלה, שרות REST מבוסס Spring MVC לקבלה ושליחת נתונים ופעולות מול הDatabase באמצעות Hibernate.

ראשית נקים את התשתיות באמצעות הגדרת הpom.xml של הMaven. למען הנוחות, הפרוייקט יכלול מודול יחיד עם כל הקוד. מומלץ לפצל פרוייקטים אמיתיים למספר מודולים כמו: persistence, dao, web וכו'.

לגרסה האנגלית: Maven, Spring, Hibernate Web Tutorial

להלן הקוד של הpom.xml:


<!--?xml version="1.0"?-->
<project xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelversion>4.0.0</modelversion>

    <repositories>
        <repository>
            <id>jboss-public-repository-group</id>
            <name>JBoss Public Repository Group</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases/</url>
        </repository>

        <repository>
            <id>SpringSource Enterprise Bundle Repository – External Bundle Milestones</id>
            <url>http://repository.springsource.com/maven/bundles/milestone</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository – SpringSource Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/release</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository – External Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/external</url>
        </repository>

        <repository>
            <id>Maven</id>
            <url>http://repo1.maven.org/maven2/</url>
        </repository>

    </repositories>

    <groupid>il.co.megasoft</groupid>
    <artifactid>spring-seminar</artifactid>
    <packaging>war</packaging>
    <version>app</version>
    <name>spring-seminar</name>
    <url>http://maven.apache.org</url>
    <properties>
        <maven.compiler.source>1.5</maven.compiler.source>
        <maven.compiler.target>1.5</maven.compiler.target>
        <webappdirectory>${project.build.directory}/${project.build.finalName}</webappdirectory>
        <project.build.sourceencoding>UTF-8</project.build.sourceencoding>
        <hibernate.version>3.6.0.Final</hibernate.version>
        <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
    </properties>
    <dependencies>
        <dependency>
            <groupid>junit</groupid>
            <artifactid>junit</artifactid>
            <version>4.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupid>org.apache.openejb</groupid>
            <artifactid>commons-dbcp-all</artifactid>
            <version>1.3</version>
        </dependency>

        <dependency>
            <groupid>taglibs</groupid>
            <artifactid>standard</artifactid>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupid>javax.servlet</groupid>
            <artifactid>jstl</artifactid>
            <version>1.1.2</version>
        </dependency>

        <dependency>
            <groupid>net.sf.ehcache</groupid>
            <artifactid>ehcache</artifactid>
            <version>[2.0.0,]</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupid>org.hibernate</groupid>
            <artifactid>hibernate-c3p0</artifactid>
            <version>3.3.1.GA</version>
        </dependency>

        <dependency>
            <groupid>com.google.gwt</groupid>
            <artifactid>gwt-servlet</artifactid>
            <version>2.2.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupid>com.google.gwt</groupid>
            <artifactid>gwt-user</artifactid>
            <version>2.2.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupid>commons-lang</groupid>
            <artifactid>commons-lang</artifactid>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupid>org.hibernate</groupid>
            <artifactid>hibernate-core</artifactid>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupid>org.hibernate</groupid>
            <artifactid>hibernate-entitymanager</artifactid>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupid>org.hibernate</groupid>
            <artifactid>hibernate-validator</artifactid>
            <version>4.1.0.Final</version>
        </dependency>

        <dependency>
            <groupid>mysql</groupid>
            <artifactid>mysql-connector-java</artifactid>
            <version>5.1.16</version>
        </dependency>

        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-context</artifactid>
            <version>3.0.0.RELEASE</version>
            <scope>runtime</scope>
            <exclusions>
                <exclusion>
                    <groupid>commons-logging</groupid>
                    <artifactid>commons-logging</artifactid>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>jcl-over-slf4j</artifactid>
            <version>1.5.8</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>slf4j-api</artifactid>
            <version>1.5.8</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>slf4j-log4j12</artifactid>
            <version>1.5.8</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupid>log4j</groupid>
            <artifactid>log4j</artifactid>
            <version>1.2.14</version>
        </dependency>

        <!-- Hibernate uses slf4j for logging, for our purposes here use the simple
            backend -->

        <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>slf4j-simple</artifactid>
            <version>1.5.3</version>
        </dependency>

        <!-- Hibernate gives you a choice of bytecode providers between cglib and
            javassist -->
        <dependency>
            <groupid>javassist</groupid>
            <artifactid>javassist</artifactid>
            <version>3.8.0.GA</version>
        </dependency>

        <dependency>
            <groupid>javax.transaction</groupid>
            <artifactid>jta</artifactid>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupid>javax.transaction</groupid>
            <artifactid>transaction-api</artifactid>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-core</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-beans</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-aop</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-context</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>org.springframework.oxm</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>org.springframework.web.servlet</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-context-support</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>com.google.collections</groupid>
            <artifactid>google-collections</artifactid>
            <version>1.0-rc4</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-tx</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-jdbc</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-orm</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-web</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-webmvc</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-test</artifactid>
            <version>${org.springframework.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-parent</artifactid>
            <version>${org.springframework.version}</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-config</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>org.springframework.security.web</artifactid>
            <version>3.0.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupid>commons-fileupload</groupid>
            <artifactid>commons-fileupload</artifactid>
            <version>1.2.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupid>commons-io</groupid>
            <artifactid>commons-io</artifactid>
            <version>1.4</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupid>org.apache.poi</groupid>
            <artifactid>poi</artifactid>
            <version>3.8-beta3</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupid>org.apache.poi</groupid>
            <artifactid>poi</artifactid>
            <version>3.8-beta4</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-war-plugin</artifactid>
                <version>2.1.1</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>exploded</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <webappdirectory>${webappDirectory}</webappdirectory>
                    <warsourcedirectory>WebContent</warsourcedirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

העדפותיי האישיות לחבילת הג'אווה המושלמת

אז מה הjava stack המועדף עליי?

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

תצורה כללית Best of bread – Non J2EE

J2EE היא טכנולוגיה טובה אבל במקרים רבים מדובר בover kill והיא עמוסה פיצ'רים ושכבות שלא נמצאות בשימוש ומכבידות על המערכת. השימוש בsession Beans למשל כשהרבה פעמים אין צורך בכך ובכלל הEJB Container המסורבל כאשר הפונקציונליות היחידה שבאמת צריך היא JPA שאפשר לקבל גם ללא EJB (באמצעות Hibernate למשל). בנוסף, בJ2EE יש נטייה להצמד לשרת של יצרן מסויים ודי מהר מתחילים להשתמש בפיצ'רים היחודיים לאותו מוצר. זה גורם לאפליקציה להיות Non Portable כשרוצים לעבור לשרת אחר. פעמים רבות נתקלתי בפרוייקטים שרצים מעל J2EE כשהשרת משמש ללא יותר מאשר Servlet Container. הפלטפורמה של JEE מסבך דברים פשוטים: WAR פשוט הופך לEAR מורכב, Deployment Descriptiors מיותרים בחלק מהגרסאות, ובגלל שהשרתים בדרך כלל תפורים מראש קשה להחליף מודול ספציפי במקרה הצורך (למשל לשדרג לגרסה חדשה יותר). לדעתי כדאי להמנע ככל שאפשר משימוש

Application Server – Apache Tomcat

שרת האפליקציות וServlet Container הנפוץ בעולם. היחיד שיכול אולי להתחרות בו הוא Jetty אבל התיעוד הרב, וותק רב השנים, האינטגרציה הטבעית עם שרת Apache כשרת WEB, והאמינות של ארגון Apache הופכים אותו לאופציה הטובה ביותר.

ORM Persistence – Hibernate JPA Annotations

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

Dependency Injection – SPRING

במקומות רבים מוותרים לגמרי על השימוש בDI ובSPRING בפרט אבל לדעתי זהו פיצ'ר חשוב שמייעל ומפשט את הקוד בצורה משמעותית. הבחירה שלי בSPRING ולא בפריימוורקים אחרים כגון Google Guise היא שSPRING הוא הרבה יותר מרק DI. במקרה הצורך אפשר למצוא מענה טוב לכמעט כל טכנולוגיית ג'אווה קיימת: JMS, JPA, Security, RMI, Web Service, מימוש טוב לAOP ועוד ועוד. היופי בSPRING הוא היותו מבוסס על POJO כך שתמיד קל להבין איך דברים עובדים והכי חשוב: אפשר לשלוט בכמות הSPRING שמשתמשים בו בקוד. החל משימוש מועט רק בDI וכלה באפליקציית SPRING מלאה כולל MVC ושאר הפיצ'רים שהפריימוורק הענק הזה מציע.

UI – Rich Client with Adobe Flex

זה אמנם לא ג'אווה ולא WEB קלאסי אבל כיום אין תחליף לפלקס וליכולות שהוא מציע באפליקציות שדורשות קליינט מורכב וברמה גבוהה. יש כמה בעיות בUI רגיל מבוסס HTML וAJAX: ראשית הקליינט לא אמין. Exception יכול לגרום לכל הדף לעוף מבלי יכולת שחזור, ניתוק זמני מהשרת גורם לעיתים קרובות לאיבוד מידע, בעיית תאימות בין דפדפנים, קושי לפתח אלמנטים גרפיים מורכבים, ועוד. שורש הבעייה בממשקי WEB נעוץ בעובדה שהממשק בסופו של דבר בנוי על HTML. הבעייה ששפה זו מלבד היותה מיושנת, יועדה במקור ליצירת מסמכים עשירים ולא לממשקי משתמש. אין גרפיקה ווקטורית, אין קומפוננטות UI נפוצות כמו Tree והקומפוננטות הקיימות מספקות פונקציונליות שמתאימה למסמך ולא לאפליקציה. קחו למשל את Table, על מנת שהוא ייראה כמו טבלה מודרנית עם אפשרות להזזת עמודות, מיונים, פילטרים וכו' צריך לכתוב קוד רב ובדרך כלל להשתמש בכלל באלמנטים אחרים כמו DIV ולא בTABLE של HTML. הפריימוורקים הרבים הקיימים מבוססים על קוד JavaScript שמבצע מניפולציות על אלמנטים בHTML ו"אונסים" אותם כך שייראו כמו UI נורמלי. הדבר היחיד שיכול לשנות את התמונה הוא HTML5 אבל יש עוד דרך ארוכה עד שנוכל להשתמש בתקן זה. ראשית צריך שהתקן יסגר ויתמך בכל הדפדפנים, אחר כך צריך שחברות יפתחו ספריות קומפוננטות מבוססות HTML5. הcanvas כנראה יספק בסיס מתאים אבל מישהו צריך לפתח ולפרסם פקדים שיהיה ניתן לעבוד איתם. כך שבנתיים מבחינת טכנולוגיות זמינות ובוגרות FLEX היא האופציה הטובה ביותר לפיתוח מהיר של ממשקים גרפיים עשירים.

שליטה על כפתורי הדפדפן (Browser) בFLEX

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

הקוד מבוסס ג'אווה סקריפט (JS) ויכול לשמש גם באפליקציות WEB רגילות.

הערה: הקוד נבדק ונמצא תומך על הדפדפנים הבאים:

  • IE 7 ומעלה
  • FireFox
  • Chrome

קוד הJavaScript שיש לכלול בדף שעוטף את הSWF:


<script type="text/javascript">
 var url = window.location;
 window.onhashchange = locationHashChanged;

 window.onbeforeunload = function(){
 return "All unsaved changes will be lost"
 }

 function locationHashChanged(){
 var currentUrl = window.location.href;
 if (currentUrl.indexOf('#c') == -1){
 var answer = confirm("Are you sure you want to leave this page? All unsave changes will be lost");
 if (answer){
 history.go(-1);
 }
 else{
 window.location = url+"#c";
 }
 }
 }

 </script>

דוגמא מלאה אפשר לראות כאן

סוד ההצלחה של ABAP ושל SAP ERP

מה זה ABAP?

ABAP הינה שפה פנימית של חברת SAP המשמשת לפיתוח אפליקציות עסקיות בתוך מערכת הERP. השפה משמשת אך ורק בתוך מערכת הSAP ואי אפשר לכתוב תוכניות ABAP מחוץ למערכות SAP. השפה פותחה בשנות ה80 ונחשבת מיושנת מבחינת סינטקס (מזכירה קצת את קובול).

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

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

אינטגרציה מלאה

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

אין הפרדה בין השכבות

כיוון שהDB הוא חלק ממערכת הSAP, וכך גם הקליינט, אין למעשה הפרדה בין שכבת הUI לשכבת הדאטה וללוגיקה. בABAP אין מודלים של MVC ואין ORM או שכבות DAL. אתה רוצה לכתוב לטבלה? השפה עצמה "מכירה" את כל הטבלאות והמבנים של המערכת ואין צורך להתחבר דרך ממשק כלשהו כפי שנעשה בדוט נט (ADO) או בג'אווה (JDCB). הGUI הוא אחיד ורץ בקליינט של SAP. לא צריך לדאוג לתמיכה בדפדפנים או להתנהגות של אלמנטים שונים בHTML.

אין Client-Server

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

אין שפות סקריפט שרצות בדפדפן, אין ניהול של sessions וcookies ולא צריך לדאוג לstate של התוכנית.

אין ריבוי משתמשים

כמובן שיש ריבוי משתמשים אבל מבחינת המתכנת הוא רץ ב"עולם" משלו והוא מסתכל רק על המשתמש שכרגע logged in.

מוגבלות זה כוח

בABAP אין ספריות native. אי אפשר לפתח API בABAP שיתממשק לפייסבוק וישלח הודעות לטוויטר. השפה נועדה רק לתוכניות עסקיות וככזו היא מוגבלת לסט הפקודות שSAP מספקת. החיסרון הוא שאי אפשר למשל לפתח רכיבי UI חדשים ותוכניות SAP תמיד ייראו באותו סגנון. מצד שני, מי בכלל צריך את זה? למה שמנהל לוגיסטי ירצה פתאום לשלב את חדשות מזג האוויר של Yahoo בתוך אפליקציית ניהול המחסנים שלו? המסכים הסטנדרטים מספקים כל מה שצריך בשביל העבודה והעובדה שאין אפשרות להכניס טכנולוגיות מבחוץ (למעט קוד ABAP אחר) מבטיחים יציבות ופשטות.

תכנות פרוצדוראלי

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

סטנדרטים קשוחים לניהול קוד

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

בנוסף, כל ניהול הגרסאות, העברה בין סביבות (DEV, QA, PROD) נעשות במתודולוגיה קבועה שמוטמעת בתוך המערכת. אי אפשר להעביר "פרוייקט" על דיסק און קי ולהעלות לשרת כמו בשפות אחרות.

לסיכום, על מנת לכתוב תוכנית ABAP מספיק להכיר את הפקודות של השפה, את הנהלים של כתיבת ABAP וכמה אלמנטים של UI. מי שתוהה איך המתכנת אמור להכיר את מאות אלפי האובייקטים (המכונים Business Object), היישויות במערכת והטבלאות אז גם לזה יש פתרון: מיישם ERP. תפקידו של המיישם להכיר את המודול הרלוונטי מכל הכיוונים והוא זה שמגדיר ומאפיין למתכנת לאילו אובייקטים לכתוב ואת מהלך התוכנית. למתכנת נשאר רק לממש את הקוד מה שמסביר למה מהנדסי תוכנה לא אוהבים ABAP…

כך נראית סביבת הפיתוח

SAP ABAP Workbench
SAP ABAP Workbench

העברת טופס לXML מAdobe Flex ActionScript3 לJava ובחזרה

כמתכנת FLEX פרילנסר עצמאי, מצאתי מקרים בהם הלקוח לא עובד בFramework מסודר לתקשורת Client-Server. במקרים כאלה לעיתים יש צורך לעשות Serialize לטופס ולהפוך אותו לפורמט גנרי כמו XML על מנת לשלוח אותו לשרת כלשהו (למשל J2EE Server) ע"י פרוטוקול כמו HTTP או SOAP Web Service. הנה פונקציה פשוטה שכתבתי שלוקחת טופס FLEX, מבצעת עליו סריקה רקורסיבית ושולפת את כל השדות שלו (TextInput) ומחזירה XML עם רשימת השדות והערכים שלהם.
כמו כן מצורף קוד בג'אווה שלוקח XML כזה והופך אותו לJava Bean ובמידה ובBean יש properties תואמים הם יקבלו את ערכי השדות. יש גם פונקציה הפוכה שלוקחת XML וממלאת את השדות בטופס FLEX.

יש להוסיף את הספריות האלה לקוד:

	import flash.utils.Dictionary;
	import mx.controls.TextInput;
	import mx.core.Container;

הנה הפונקציה (למעשה שתי פונקציות) שלוקחת טופס ומחזירה XML:

		public static function viewToXml(className:String, view:Container):String{
			var xml:XML = <bean class={className}></bean>;
			var xmlFinal:XML = addControlsToXml(xml, view);
			return '<?xml version="1.0" encoding="UTF-8"?>' + xmlFinal;
		}
		
		private static function addControlsToXml(xml:XML, view:Container):XML{
			var controls:Array = view.getChildren();
			for (var i:int; i<controls.length; i++){
				if (controls[i] is TextInput){
					var component:TextInput = controls[i] as TextInput;
					var child:XML = <property name={component.id}>{component.text}</property>;
					xml.appendChild(child);
				}
				if (controls[i] is Container){
					addControlsToXml(xml, controls[i] as Container);
				}
			}
			return xml;
		}

והפונקציה שמקבלת XML וממלאת את הטופס בהתאם:

		private static function xmlToView(props:Dictionary, view:Container):void{
			var controls:Array = view.getChildren();
			for (var i:int; i<controls.length; i++){
				if (controls[i] is TextInput){
					var component:TextInput = controls[i] as TextInput;
					if (props[component.id] != null){
						component.text = props[component.id];
					}
				}
				if (controls[i] is Container){
					xmlToView(props, controls[i] as Container);
				}
			}
		}

ועכשיו לקוד בג'אווה. תצטרכו להוסיף את הספריות הבאות:

import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

קבלת XML והפיכתו לJava Bean:

	public static Object xmlToBean(String xmlStr){
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = null;
		try {
			documentBuilder = documentBuilderFactory.newDocumentBuilder();
			Document doc = documentBuilder.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));
			XPath xpath = XPathFactory.newInstance().newXPath();
			Node bean = (Node)xpath.evaluate("//bean", doc, XPathConstants.NODE);
			if (bean != null){
				NamedNodeMap attributes = bean.getAttributes();
				Node cls = attributes.getNamedItem("class");
				String clsName = cls.getNodeValue();
				Object obj = Class.forName(clsName).newInstance();
				NodeList props = (NodeList)xpath.evaluate("//property", doc, XPathConstants.NODESET);
				Class cl = obj.getClass();
				for (int i=0; i<props.getLength(); i++){
					String propName = props.item(i).getAttributes().getNamedItem("name").getNodeValue();
					String propValue = props.item(i).getTextContent();
					String ch = propName.substring(0, 1).toUpperCase();
					String methodName = "set" + ch + propName.substring(1);
					Method[] methods = cl.getMethods();
					for (Method method : methods){
						if (method.getName().equals(methodName)){
							Class[] paramTypes = method.getParameterTypes();
							for (Class param : paramTypes){
								if (param.getName().equals("java.lang.String")){
									method.invoke(obj, propValue);
								}
								else if(param.getName().equals("int")){
									method.invoke(obj, Integer.valueOf(propValue));
								}
								else if(param.getName().equals("long")){
									method.invoke(obj, Long.valueOf(propValue));
								}
							}
						}
					}
				}
				return obj;
			}
		} catch (Exception e) {
			log.error(e);
		}
		return null;
	}

והחלק האחרון: לקחת Java Bean ולהפוך אותו לXML:

	public static String beanToXml(Object obj){
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = null;
			try {
				documentBuilder = documentBuilderFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				log.error(e);
			}
			Document doc = documentBuilder.newDocument();
			Element rootElement = doc.createElement("bean");
			rootElement.setAttribute("class", obj.getClass().getName());
			Method[] getters = obj.getClass().getMethods();
			for (Method getter : getters){
					if (getter.getName().indexOf("get") == 0)
					if (getter.getParameterTypes().length == 0)
					if (!getter.getName().equals("getClass")){
						StringBuffer prop = new StringBuffer(getter.getName().substring(3));
						String ch = prop.substring(0, 1).toLowerCase();
						prop.replace(0, 1, ch);
						
						Element propElement = doc.createElement("property");
						propElement.setAttribute("name", prop.toString());
						try {
							propElement.setTextContent(getter.invoke(obj).toString());
						} catch (Exception e) {
							e.printStackTrace();
						}
						rootElement.appendChild(propElement);
					}
			}
			doc.appendChild(rootElement);
			
			StringWriter stw = new StringWriter(); 
            Transformer serializer;
			try {
				serializer = TransformerFactory.newInstance().newTransformer();
				serializer.transform(new DOMSource(doc), new StreamResult(stw));
			} catch (Exception e) {
				log.error(e);
			}
            return stw.toString();
	}

איך לעבור מבחן וראיון מקצועי בחברת הי-טק

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

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

  1. כמה זמן יארך המבחן?
  2. מי מעביר את המבחן?
  3. האם המבחן הוא בעל פה במתכונת של ראיון, מבחן על דף או מבחן על מחשב?
  4. מהם נושאי המבחן בכלליות?

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

ככלל, קיימים שלושה סוגים עיקריים של מבחנים:

  • מבחן בעל פה מול מראיין
  • כתיבת משימת קוד
  • קובץ של שאלות בדרך כלל אמריקאי

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

מבחן בעל פה מול מראיין

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

דבר על משהו שאתה שולט בו ב100%

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

בחר נושא עם מימוש מעניין שאפשר לפתח סביבו דיון

הצגת הודעת משתמש על המסך הוא לא משהו משמעותי למשל. עליך לבחור משהו שיכלול טכנולוגיה מעניינת, מימוש מורכב ורצוי בעל מאפיינים קלאסים כגון MVC, ירושה ותכנון OOP מורכב, שימוש מיוחד בטכנולוגיה. נושאים מעולים לדיון יהיו: פתרון של בעיית ביצועים, מימוש של Caching, Clustering, וכו', ארכיטקטורת שכבות כלשהי: DAO, ORM, אלגוריתמים כמו: עצי משחק, מכונות מצבים, פתרונות באמצעות תורת הגרפים, בעיות Multi Threading וסינכרון.

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

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

בחר משהו מהניסיון הרלוונטי ביותר

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

הכן פרוייקט לגיבוי

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

מה לגבי שאר השאלות?

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

כתיבת משימת קוד

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

הכנה מוקדמת

ברוב המקרים תידרש לכתוב תוכנית console שמשתמשת בקלט פלט סטנדרטי בשורת פקודה. מעבר לזה רוב המימוש יכלול את רכיבי השפה הבסיסיים: מערכים, מבני נתונים, ניתוח Strings, וכו'. כדאי לחזור על דברים בסיסיים שאולי שכחת על מנת שלא לבזבז זמן יקר: פתיחת פרוייקט חדש בסביבת הפיתוח, פקודות קלט פלט למסך, הגדרת קלאסים מסוגים שונים: interface, abstract, החבילות המכילות את מבני הנתונים הנפוצים   (List, Hash) ועוד. עוד נושא קריטי להצלחה במבחן הוא: Design Patterns. DP הוא נושא שיחסית קל ללמוד ולזכור ומאידך שימוש נכון בDP משאיר רושם טוב ומקצועי. כדאי מאוד להכיר את התבניות של הGOF ולדעת איך להשתמש בהן.

להלן דגשים למבחן בכתיבת קוד:

אל תתעכב על קריאת האפיון ואל תרד לפרטים

הרבה פעמים האפיון יתפרס על כמה עמודים באנגלית ולקרוא אותו עד הסוף ולהבין אותו באופן מלא יכול לקחת 15-20 דקות. זהו זמן יקר בהתחשב בעובדה שרוב המבחנים אורכים כשעתיים (זמן מוגבל). כדאי לקרוא ברפרוף ולעבור לדוגמאות (בדרך כלל מופיעות במסמך) ולהבין בגדול מה מצפים. אף אחד לא יפסול אותך כי המבחן הדפיס "hello moshe" במקום ""hello david.

הקדש 10 דקות לתכנון הפתרון

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

לעולם תתחיל מהגדרת כל המחלקות והפונקציות

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

סטנדרטים מומלצים

חלק גדול מהמבחן הוא לבדוק האם המועמד משתמשת בBest Practices ובשיטות מקובלות ועד כמה הקוד שלו איכותי.

שמות מחלקות, משתנים, ומתודות

יש להקפיד על שמות בסטנדרט מקובל. מועמד שיקרא למשתנים שלו: a,b,c במקום: customer, person, manager יאבד נקודות במהירות. כנ"ל לגבי שימוש נכון באותיות גדולות וקטנות כפי שמקובל בשפה הרלוונטית.

תיעוד

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

שימוש מושכל בפולימורפיזם

בג'אווה למשל, אפשר ליצור רשימה בכל אחת מהדרכים הבאות:

ArrayList list = new ArrayList ();

List list = new ArrayList();

List<String> list = new ArrayList<String>();

על אף שהתוצאה בשלושת המקרים תהיה זהה המקרה השלישי הוא הנכון ביותר. הרשימה מוגדרת כList שהינו interface כך שניתן להחליף את המימוש בקלות (למשל לLinkedList). הרשימה היא type safe כך שמובטח שתכיל רק אובייקטים מסוג String.

שימוש נכון בלולאות

נניח ויש לנו את הרשימה מהדוגמא הקודמת. כיצד נדפיס את אברי הרשימה?

For (int i=0; i<list.size(); i++)

System.out.println(list.get(i)):

(for String s : list)

System.out.println(list.get(i)):

כדאי להכיר את הלולאה for each (החל מג'אווה 1.5) ולהשתמש בה כשאפשר.

Design Patterns מומלצים

סטטיסטית, כמעט כל מרואיין שנשאל אילו DP הוא מכיר יענה Singleton או Factory. כדי להראות מקצועיות כדאי להכיר עוד כמה DP מלבד השניים המוכרים הללו. במבחנים המקצועיים יש כמה DP שיכולים להיות שימושיים, ואילו אחרים בדרך כלל לא יהיו רלוונטים. נהוג לחלק את רשימת הDP של הGOF לשלושה תתי רשימות: Creational Patterns, Structural Patterns, Behavioral Patterns. הStructural כמעט ולא רלוונטים למבחנים כיוון שבדרך כלל בשעתיים אי אפשר להגיע לרמות OO שמתאימות לדפוסים אלה. למעט Composite שכדאי להכיר כי בהרבה מבחנים מבקשים להגדיר מבנה בצורת עץ היררכי כלשהו.

הCreational מתאימים במקצת אם כי לרוב אין צורך בהם שוב בגלל שהקוד מצומצם למספר קטן של קלאסים.

הBehavioral הם המתאימים ביותר ורצוי לשנן אותם ולהשתמש בהם בעת הצורך.

מבחן אמריקאי

לרוב קשה להתכונן למבחן אמריקאי כיוון שאין לדעת אילו נושאים יופיעו ועל מה יהיה המיקוד. אבל ישנו דפוס שחוזר על עצמו לעיתים קרובות וכדאי להגיע מוכן אליו. כיוון שבכל תחום בעולם ההי-טק קיימים מאות טכנולוגיות שונות, ספריות, API, פלטפורמות וחבילות תוכנה אין כל כך טעם לבחון מועמד על טכנולוגיה מסויימת כיוון שקיים סיכוי טוב שאותו מועמד לא השתמש בטכנולוגיה זו ולכן לא יידע לענות על השאלות מה שלא מעיד על הידע והיכולות האמיתיות שלו. לכן נהוג לקחת את המכנה המשותף הנמוך ביותר שמצופה מכל מועמד להכיר וזה בדרך כלל החוקים של שפת התיכנות עצמה ונושאים סביב הליבה של הטכנולוגיה. הנושאים הנפוצים ביותר הנשאלים הם על: OOP וחוקי ירושה, פולימורפיזם, Up casting, down Casting, בעיות סינכרוניזציה וmulti Threading, מבני נתונים בסיסיים (רשימות, עצים בינארים), מערכים ומחרוזות, העברת פרמטרים By Value וby Reference, טיפול בexceptions כולל חוקי ההיררכיה, שאלות על פונקציות שונות במחלקות String וobject (רלוונטי לג'אווה ולדוט נט), Reflection, שימוש בbreak וcontinue ועוד.

בין הנושאים המתקדמים יותר אפשר למצוא: כוונון של הGC, JVM/CLR, סוגים שונים של Collections ודרך התנהגותם, מילות שמורות בשפה שנדיר להשתמש בהם, דקויות בין טיפוסי משתנים והמרות (int, long, double, float, char).

כללים לפיתוח אבטיפוס, הדגמה (Demo), ובדיקת התכנות (POC)

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

קוד גנרי או פתרון זמני?

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

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

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

להתמקד במינימום ההכרחי

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

רכיב

המלצה

הערות בקוד

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

Log

אין צורך. המוצר ממילא לא ירוץ בProduction והוא פשוט מספיק כדי לדבאג במקרה הצורך

Unit Tests

לאנשי הTDD ברור שצריך. לאחרים – מומלץ בדיקות פשוטות בלי Mocks ושאר סביבות מתוחכמות

Integration Tests

אין צורך

שימוש בFrameworks

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

משתנים Hard coded

לא צריך להשאיר דברים hard coded כמו מחזרוזות בתוך פונקציות. עדיף להוציא הכל למשתני const. מצד שני אין צורך להוציא דברים החוצה (XML, DB, קובץ) אפשר לשנות את הקוד במידה ורוצים לערוך משהו

OOP

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

Bayby Agile

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

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

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

יש למה לחכות…

מפתח פרילנסר: עשה ואל תעשה בעבודה מול לקוח

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

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

הכנסת טכנולוגיות מגוונות מדי לארגון
עוד בעיה נפוצה היא שיועצים עצמאים נוטים למצוא את הכלי המתאים ביותר לפתרון הספציפי מבלי להתחשב באסטרטגית הIT של הארגון. בחברה מסויימת היה ERP של SAP, כמה אפליקציות דוט נט, בסיס נתונים של אורקל ופורטל ארגוני של SAP Portal. כשהם ביקשו לפתח אפליקציה תפעולית מסויימת, הקבלן בחר בOracle JDeveloper על שרתי Oracle IAS לפיתוח התוכנה. התוכנה היתה תוכנה קטנה ששרתה צורך מסויים והבעיה נוצרה כשבארגון "צמח" לפתע סט שרתי חדשים, סביבת פיתוח, ופלטפורמה שצריך לתחזק. זה דרש הקצאה של כוח אדם, יועצים, חומרה ורשיונות תוכנה רק בשביל אותה אפליקציה. אז אומנם הפיתוח הראשוני היה מהיר אבל התחזוקה רבת השנים שלא לדבר על שינויים ותיקונים גררו את הלקוח להוצאות כבדות ומיותרות. הפתרון הנכון היה לבחור באחת הפלטפורמות שנמצאות כבר בארגון ולפתח על בסיסה.

עומס טכנולוגיות – Over Kill
בעיה נוספת שמאפיינת בעיקר את הפרילנסרים חובבי הטכנולוגיות והגאדג'טים הוא שימוש מופרז בכלים מתוחכמים שבסופו של יום לא מספקים ערך מוסף ללקוח או אפילו פוגעים בו. הרבה פעמים מתכנתים רוצים להשתמש בטכנולוגיות המשוכללות והנוצצות ביותר כשמספיק משהו הרבה יותר בסיסי. למשל, שימוש נפוץ בשרתי JBoss ובJ2EE מנפחים פרוייקט ג'אווה כשהרבה פעמים כל מה שצריך הוא Servlet Container כמו Tomcat בשילוב עם Hibernate. אין טעם להשתמש בשרת אפליקציות כבד אם לא מתכוונים לנצל את הפונקציונליות שלו.

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

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

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

לפתור בעיה בלתי פתירה

אסייג ואומר שמאמר זה מיועד בעיקר למתחילים אבל גם מתכנתים וותיקים יכולים למצוא בו עניין. פעמים רבות אנחנו נתקלים בבעיה בעבודה שבמבט ראשון נראית בלתי פתירה. אני אישית, אני מאמין שההבדל בין מתכנת טוב למתכנת מעולה הוא ביכולת לעקוף ולמצוא פתרונות שלא נפתרים באמצעות ההגיון הפשוט. אני לא מדבר על אלגוריתם מסובך או קוד שלא רץ כראוי – בשביל זה יש מספיק כלים ומתודולוגיות. אני מדבר על מצבים שבדרך כלל לא נמצאים בשליטתנו.
דוגמא: אתה מפתח מול שרת אפליקציות ובשלב כלשהו כל נסיון להעלות את האפליקציה (Deploy) זורקת שגיאה: The server failed to deploy the application: error #4522
אתה בטוח שלא עשית שום דבר חריג, אין שום דרך לדבאג את זה ואין שום שינוי בקוד שיכול לתקן את זה. האפשרות הסבירה ביותר למרבית האנשים היא לפנות למישהו מנוסה "תותח" כמו שאוהבים לכנות אותם שיפתור את הבעיה. אתה תצא בסדר: זה לא באג בקוד שלך ולא עשית שום דבר רע. אבל האם שאלת את עצמך פעם איך עובד כלשהו הגיע למצב שהוא נחשב "תותח"? למה שלא תהיה אחד כזה בעצמך?
כמובן שמרכיב הניסיון הוא חשוב אבל יש עוד דברים שמשפיעים על יכולת פתרון בעיות. יש מתכנתים שאחרי שנתיים נחשבים למומחים ויש אחרים שגם אחרי 10 שנים נשארים בינוניים. להלן סדר פעולות שיכול לסייע בפתרון של כל בעיה:

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

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

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

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

חיפוש בגוגל ובאינטרנט
יעיל במיוחד בעבודה מול מערכות חיצוניות. העתק את שורת השגיאה ותריץ בגוגל. בהרבה מקרים ה2-3 תוצאות הראשונות יתנו הסבר מדויק של הבעיה ופתרונה. לפעמים צריך קצת להוסיף מילים לשורת החיפוש כמו: "deployment error #333 WebLogic 8.1" אבל העניין ברור. 20 שניות שיכולות לחסוך שעות של עבודה.
אגב, בדרך כלל חיפוש בגוגל יעיל יותר מחיפוש באתר החברה. גוגל פשוט מאנדקס את העמודים בצורה הרבה יותר יעילה ממרבית מנועי החיפוש הפנימיים. נסו לחפש מידע בגוגל לעומת MSDN או האתר של אורקל ותמצאו שגוגל נותן תוצאות הרבה יותר טובות. רק אם לא מוצאים בגוגל כדאי לחפש באתרים אחרים דבר שבדרך כלל לוקח יותר זמן.

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

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