Source: www.jroller.com/rss/ara_e

Memory Dump


J3.1EE! :-)

A few years ago I wrote a blog entry called J3EE and defined what I wanted to see in a hypothetical future version of J2EE. These days J2EE is called JEE apparently but here I want to upgrade the J3EE idea to a shiny new J3.1EE version number :-) Note that it has nothing to do with JEE official version numbers.

In that old article I praised Spring as "The most complete pseudo-J3EE container available right now." But I think these days it can be said that Spring looks rather "old" compared to other frameworks that came to further advance the idea. These days it can be said that many things in Spring are simply "legacy", the blablaTemplate classes for example, or the TM abstraction. In those good old days, AOP, IoC/DI, Annotations, and Lightweight configurable enterprise services were promising a better future for enterprise java development. Here is an update on their statuses:

AOP

AOP has moved forward quite a bit. The frontier in this space is still Spring with its fantastic AOP support in form of a powerful AspectJ based pointcut language and built-in aspects. Plus, aspect definitions are POJOish now. I don't know about you, but I use AOP, and by AOP I don't mean lame interceptors like EJB3's. A flexible, but yet sophisticated framework such as Acegi couldn't have existed without true AOP.

DI

The funny "to be setter based or constructor based" DI debate has disappeared. The new debate is on the way we define dependencies. Personally, I really like the way Google Guice handles it. It's a damn good use of annotations and in my opinion a very good trade off between flexibility (Spring) and ease of use (Seam). And it's the only type safe solution out there.

Annotations

Gavin certainly is the King of annotations :-) JBoss Seam, Hibernate/EJB3 annotations are some very beautiful usages of annotations. Compare Hibernate annotations to the old cfg.xml files. Annotations are cleaner and easier to use definitely. God bless the guys who popularized annotations ;-) See how Seam's elegant use of annotations leads to a much easier to use and "modern" framework compared to Spring WebFlow which looks completely old fashioned!

Lightweight configurable enterprise services

Spring definitely still has the most complete support for this, but JBoss seems to be closing the gap: Seam's 89MB download bundle is an evidence of that :-) Besides, JBoss adds some interesting toys to mix, for example JBPM and its usages in Seam and other places. I'm looking forward to JEE 6 profiles and how these profiles would make JEE less monolithic and possibly provide better plugin points for defining and managing these services. I would really like to be able to easily add, let's say the JMS profile to my websphere deployment whenever I need it. Or a portlets profile, and so on. I'm also very excited about OSGI and I'm waiting to see how Spring OSGI will look like. I also would like to see more off the shelf beans/services exposed via POJOish JMX beans.

J3.1EE :-)

A mix of Spring 2's rich AOP, a type safe and pragmatic DI framework like Guice, a good dose of annotations in a "modern" framework like Seam, and even more lightweight and dynamic services (profiles, OSGI, JMX) seem to be parts of my 3.1 upgrade.

I still am not quite excited about some technologies that have been, let's say, forcefully added to the mix, such as JSF. But Seam and the future Web Beans spec seems to fix most of its problems though I just don't like some parts of the JSF core architecture and with AJAX things are quite different from the days JSF was designed. Nonetheless, I believe we definitely need more flexible/customizble JEE building blocks  (profiles) and a much easier programming model to compete with Rails. I've been working on a startup public site recently and given the kind of time constraints that startups have I can completely understand why Rails is such a brilliant infrastructure in such cases.

Ara.



Profiling Framework

Hola! I’m still alive and I?m back with a new blog post on profiling and a little profiling framework too!

Recently I had to help a team in profiling and optimizing a huge application, which is deployed on a mainframe and has a pretty sophisticated logic. I couldn't use a profiler tool, such as JProfiler. Most of these tools have hooks deep inside the JVM and you?re out of luck when you don?t have enough control on the environment on which the application is deployed. And of course to get a more realistic view of the application, you must profile it under real world loads, so you can?t pause or slow down the application to look around in the app using a profiler tool. And sometimes you need to profile an already deployed application because under some circumstances it performs rather weakly. So you need a tool for metering the performance which could be enabled/disabled dynamically and focused for gathering performance data only for the problem at hand.

And that?s why I created my little profiling framework!

I have used YourKit profiler before. I know you can get on demand snapshots of heap using this tool. I?ve used Atlassian-profiling too, which lets you see a nice call stack of code. It?s useful for development, when you want to see which methods where called and how long each of them took. But it?s for a single call sequence, for example for a single http request. You can?t calculate TPS with it because to calculate it you need to fire up a bunch of concurrent requests and calculate TPS as a whole. And I knew Spring has a StopWatch class, and an interceptor which wraps beans and uses this stop watch, but nothing more than that.

So I created a very simple framework for profiling "execution speed" in a very flexible and configurable manner. It does nothing for memory profiling.

It consists of a StackedStopWatch class and has an interface similar to Atlassian-profiling:

StackedStopWatch.start(?deposit?); try { StackedStopWatch.start(?depositValidation?); //deposit validation code here StackedStopWatch.stop(?depositValidation?); //deposit code here... } finally { StackedStopWatch.stop(?deposit?); }

Under the hood, StackedStopWatch creates StopWatchExecution objects which collect performance data for stop watches. Like atlassian-profiling it keeps a tree of StopWatchExecution objects, so later I can print out a nice call stack of all calls, but unlike atlassian-profiling it's logged using commons-logging instead of System.out.println() on the console. So, if I enable logging for "StackedStopWatch", "deposit" and "depositValidation" let's say in log4j's log4j.properties (or in case of JDK 1.4 logging in logging.properties), I get an output like this in my logging file or console or on a socket or wherever I've configured logging to log to:

[571ms] - deposit [123ms] - depositValidation

There's also a servlet filter called ProfilingFilter which can be used to log http request/response performance. And also an interceptor I can configure to automatically log method calls:

<bean id="performanceMontitor" class="org.springframework.util.perf.SmartPerformanceMonitorInterceptor"> <property name="useDynamicLogger" value="true"/> </bean> <aop:config> <aop:pointcut id="allMethods" expression="execution(* *..*.*(..))"/> <aop:advisor advice-ref="performanceMontitor" pointcut-ref="allMethods"/> </aop:config>

Here I attached this interceptor to all methods of all beans "but it logs only calls which are configured to be logged". I can enable logging for everything except requests for /img/* and /attachment/* urls and some DAO class named com.myapplication.dao.HibernateReportGeneratorDao, as below:

com.myapplication.level=FINEST org.springframework.util.perf.web.ProfilingFilter.img.level=OFF org.springframework.util.perf.web.ProfilingFilter.attachment.level=OFF com.myapplication.dao.HibernateReportGeneratorDao.level=OFF

And then I get a log like this for a request to /some/url:

[1733ms] - org.springframework.util.perf.web.ProfilingFilter.some.url [1207ms] - com.myapplication.web.DepositController.handleRequest [199ms] - com.myapplication.dao.HibernateDepositDao.findDepositData [180ms] - com.myapplication.dao.HibernateUserDao.findUser

This is pretty useful during development. For profiling a deployed application we need to put it under real load and collect detailed profiling data once in a while periodically and analyze these logs. I've written a JMX bean for this purpose. It only has one method which when called logs a table like this:

------------------------------------------------------------------------------------------------ s % # tps Task name ------------------------------------------------------------------------------------------------ 00000.1 000% 00001 00012.5 com.myapplication.security.entity.dao.HibernateRoleDao.getAll 00000.1 000% 00012 00011.1 com.myapplication.entity.dao.HibernateTagDao.getAll 00000.1 001% 00001 00010.0 com.myapplication.entity.dao.HibernateTagDao.findPrimaryTags 00000.2 001% 00001 00005.9 com.myapplication.security.entity.dao.HibernateUserDao.getAll 00000.2 001% 00001 00005.6 com.myapplication.entity.dao.HibernateTagDao.findPopularTags 00000.2 001% 00001 00005.5 com.myapplication.entity.dao.HibernateTagDao.getAverageRelatedTagsFor 00000.3 001% 00001 00003.7 com.myapplication.entity.dao.HibernateTalkDao.getAll 00000.3 002% 00001 00002.9 com.myapplication.bucket.entity.dao.hibernate.HibernateEntityObjectDao.getAll 00000.6 003% 00005 00001.6 org.springframework.util.perf.web.ProfilingFilter.user.ara.largephoto 00000.7 004% 00002 00002.8 com.myapplication.security.entity.dao.HibernateUserDao.findUserByUsername 00000.8 004% 00001 00001.3 org.springframework.util.perf.web.ProfilingFilter.user.armond.largephoto 00002.0 010% 00015 00000.5 com.myapplication.entity.dao.HibernateTalkDao.findTalksForTag 00002.1 011% 00020 00000.5 com.myapplication.web.talk.ViewTagController.handleRequest 00017.3 088% 00020 00000.1 org.springframework.util.perf.web.ProfilingFilter.some.url

This data is generated from all the StopWatchExecutions collected up until now. You can see total seconds, percentage of each call compared to the total, how many times each one was called and a TPS for each one.

Normally someone calls you and complains about some feature of the application being slow. You enable loggings, analyze the logs, disable some logs for focusing on the logs related for the problem at hand, play with the configuration or the code to boost up performance, and end up with logs that show this performance improvement.

Sometimes you need to see the arguments a specific method was called with. You can enable logging more information:

more.com.myapplication.entity.dao.HibernateTalkDao.findTalkByEscapedSubject.level=FINEST

And it logs arguments too:

[1733ms] - org.springframework.util.perf.web.ProfilingFilter.some.other.url [1207ms] - com.myapplication.web.DepositController.handleRequest [199ms] - com.myapplication.entity.dao.HibernateUserDao.findUser (ara) (joe)

Because I used logging in this framework, you can dynamically enable/disable logging and change logging levels, for example by using JDK's JConsole utility. So whenever you want to profile something, just fire up jconsole, enable logging for a piece of code, collect detailed logging data, analyze it, tune the configuration or change the code, and hopefully if that change doesn't force you to restart the server just disable logging and let the users enjoy the improved performance!

And I put this code in org.springframework.util.perf package 'cause I think it must be built into Spring :) You can download the code from here. Let me know if it's useful and then we'll find a way to either contribute it to an existing open source project or host it somewhere else.... It certainly could be enhanced to support more sophisticated profiling scenarios.

Ara.



POWO (Plain Old Workflow Object)

Recently I was involved in a workflow-based project. The project uses OSWorkflow heavily and has various sophisticated workflows. What I noticed from looking at the workflow related code was how ugly and complicated the code is, or becomes over time. It's because of the implicit programming model of these workflow frameworks (or whatever you call them: graph oriented programming, bla bla). So the xml file defining the workflow has lots of get/set("thisorthat") of properties attached to a workflow instance like this:

<result id="412" due-date="" old-status="Finished" status="Queued" step="3"> <conditions> <condition type="beanshell"> <arg name="script"><![CDATA[ propertySet.getString("check.inventory.result").equals("none") ]]></arg> </condition> </conditions> <post-functions> <function type="class"> <arg name="order.new.status.id">NOT_AVAILABLE</arg> <arg name="order.old.status.id">ORDER_APPROVED</arg> <arg name="service.name">OrderBizLogicService</arg> <arg name="class.name">util.osworkflow.BizLogicServiceFunctionProvider</arg> <arg name="method.name">persistOrderStatus</arg> </function> </post-functions> </result>

This is a step of the workflow which checks whether a property has been set to "none" and triggers a persistOrderStatus method in a bean to do something with the workflow, like setting some more properties and saving some stuff in the database. The code for the bean looks like this:

public class OrderBizLogicServiceBean extends BizLogicServiceBean { ... public void execute(Map transientVars, Map args, PropertySet ps) throws ServiceException { String methodName = (String) args.get(METHOD_NAME); WorkflowEntry workflowEntry = (WorkflowEntry) transientVars.get(WORKFLOW_ENTRY); long workflowId = workflowEntry.getId(); if ("persistOrder".equals(methodName)) { /* gets ORDER_HEADER from transientVars */ OrderHeaderValue orderHeaderVO = (OrderHeaderValue) transientVars.get(ORDER_HEADER); /* gets ORDER_ITEMS from transientVars */ OrderItemValue[] orderItemVOs = (OrderItemValue[]) transientVars.get(ORDER_ITEMS); /* gets ORDER_ROLES from transientVars */ OrderRoleValue[] orderRoleVOs = (OrderRoleValue[]) transientVars.get(ORDER_ROLES); /* gets USAGE_ID from transientVars */ String usageId = (String) transientVars.get(USAGE_ID); /* invoke persistOrder() method */ String orderId = persistOrder(orderHeaderVO, orderItemVOs, orderRoleVOs, workflowId, usageId); /* put ID of newly created OrderHeader in PropertySet */ ps.setString(ORDER_ID, orderId); /* Do some DAO calls here.... */ /* calls persistOrderStatus() method */ } else if ("persistOrderStatus".equals(methodName)) {...} }

It's hell of an ugly code, all those get/set calls to get or put some stuff attached to this instance of workflow is very ugly, unreadable and error prone.

As far as I've seen, this style of "implicit" coding is the norm in all workflow/graph/bpm/etc frameowrks of this kind. Some properties are attached to the workflow in some step of the execution, somewhere else some of those properties are fetched, some juggling is done and yet some more properties with some literal names are attached to the workflow instance which will be later used in some other step or substep of the flow.

You can imagine how easily this style of coding can lead to disaster. It's hard to trace what's there in the workflow instance, what has happened so far. There's no one to one mapping from the workflow definition file to any piece of business logic code which deals with the workflow. Everything is implicit, get this literal, set that, no compile time help of any kind. What if you misspell one of those properties or the method name or ...? And the whole workflow API is directly exposed to your business logic code, which is bad, because business logic must be only business logic not low level workflow API calls in obscure if/else checks. It's all implicit. Implicit code is flexible but hard to maintain. It all leads to unreadable and ugly code.

Hell no! Not my code! So I came up with a different, a more explicit, workflow programming model, based on what I call POWOs (plain old workflow objects) or workflow beans. Yes guys! Yet another POxO!! In this model I tried to make things more explicit, with no calls to any workflow API in the business logic code.

So for each workflow definition file, there's a "code-behind" workflow bean or POWO. It's like jsp/asp really. There's the tag and you know there's a bean mapped to that tag. It's obviously easier to track:

   
<action id="1" name="Start Workflow">
      <
pre-functions>
        <
function type="workflowbeanshell">
          <
arg name="script"><![CDATA[
            workflowBean.setPrOrderId("666")
         
]]></
arg>
        </
function>
        <function type="workflowbeanshell">
          <
arg name="script"><![CDATA[
            workflowBean.persistOrderStatus("NOT_AVAILABLE", "ORDER_APPROVED")
         
]]></
arg>
        </
function>
      </
pre-functions>
    </
action>

In the code above, instead of explicitly using propertySet.setString() we call a setter method on the powo bean of this workflow. persistOrderStatus() is also defined in that powo and we simply call that method. The powo itself looks like this:

public class SampleWorkflowBean extends WorkflowBean {    private Object workflowManager;    private String prOrderId;    private String trSomething;    public void setWorkflowManager(Object workflowManager) {        this.workflowManager = workflowManager;    }

    /** use "Pr" prefix for all attributes of Propertyset */

    public void setPrOrderId(String prOrderId) {

        this</B>.prOrderId = prOrderId;

    }

    public String getPrOrderId() {

        return prOrderId;

    }

   /** use "Tr" prefix for all attributes of TransientVars */

    public String getTrSomething() {

        return trSomething;

    }

   public void setTrSomething(String trSomething) {

     &nbsp;  this.trSomething = trSomething;

    }

   /** A function called from within workflow xml file "Sample.xml" */

    public void persistOrderStatus(String orderNewStatusId, String orderOldStatusId) {

        setPrOrderId("123");

        //save the order, do whatever...

        //and to work with a transientVar instead of this:

        //String something = (String) transientVars.get("something");

        //do like this:

        String something = getTrSomething();

        //this is the id of the workflow we're operating on:

        long workflowId = getWorkflowId();

        //workflowManager is dependency injected from Spring context xml file:

        workflowManager.createWorkflowReference("OrderHeader", getPrOrderId(), workflowId);

    }

}

Looks like a pojo! There's no transientVars, propretySet or WorkflowEntry or anything like that. Somehow magically all those getter/setter methods are intercepted (using Spring) and delegated to the proper propertySet/etc object: public class AttributeFlusherInterceptor implements MethodInterceptor {     public Object invoke(MethodInvocation invocation) throws Throwable {         Method method = invocation.getMethod();         String methodName = method.getName();         Object rval = null;         if(methodName.startsWith("setTr")) {             String capAttributeName = methodName.substring(5);             String attributeName = StringUtils.uncapitalize(capAttributeName);             WorkflowContext.getWorkflowContext().getTransientVars().put(attributeName, invocation.getArguments()[0]);         } else if(methodName.startsWith("getTr")) {             String capAttributeName = methodName.substring(5);             String attributeName = StringUtils.uncapitalize(capAttributeName);             rval = WorkflowContext.getWorkflowContext().getTransientVars().get(attributeName);             invocation.getThis().getClass().getMethod("setTr" + capAttributeName, new Class[]{rval.getClass()}).invoke(invocation.getThis(), new Object[]{rval});         } else if(methodName.startsWith("setPr")) {             String capAttributeName = methodName.substring(5);             String attributeName = StringUtils.uncapitalize(capAttributeName);             WorkflowContext.getWorkflowContext().getPropertySet().setAsActualType(attributeName, invocation.getArguments()[0]);         } else if(methodName.startsWith("getPr")) {             String capAttributeName = methodName.substring(5);             String attributeName = StringUtils.uncapitalize(capAttributeName);             rval = WorkflowContext.getWorkflowContext().getPropertySet().getAsActualType(attributeName);             invocation.getThis().getClass().getMethod("setPr" + capAttributeName, new Class[]{rval.getClass()}).invoke(invocation.getThis(), new Object[]{rval});         }         rval = invocation.proceed();         return rval;     } }

The code for creating a new workflow instance and calling an action of it is as simple as this:

SampleWorkflowBean sampleWorkflowBean = (SampleWorkflowBean) springAppContext.getBean("sampleWorkflowBean"); SampleWorkflowBean bean = (SampleWorkflowBean) sampleWorkflowBean.create(); bean.doAction("11");

doAction() and create() are two of the methods defined in the WorkflowBean base class.

The powo derives from WorkflowBean but the powo is a pure pojo.

So in the end for every workflow there's a corresponding powo for it, and it's as clean as possible. By looking at the powo we see a list of all the properties of a workflow (as bean accessors), and they are accessed only via this powo with no sloppy get("thisorthat"). This model has a few fantastic side-effects too. For example, if you have 2 workflows both dealing with orders, you define a base class for both and move the common properties up to that base powo! It encourages clean hierarchies and reuse.

There are a few other classes involved too, but I won't describe them here, this post is already too long! I wonder if anyone else finds this way of working with workflows interesting? I may be able to opensource the relevant parts.



JavaPolis, Dec 14

So here is my report from the first day of JavaPolis 2004 :-) This is my first time here in Antwerp.

I went to Adrian Colyer's AspectJ in Action presentation and then to Joshua Bloch and Neal Gafter's JDK 5.0 in Action talk. They were both interesting, even though I was familiar, to some extent, with both topics.

What caught my attention from the AspectJ talk was the idea of Architecture enforcement. Basically it means using "declare warning" to define an aspect for showing warning messages when architecture forbids something and some piece of code coded by a silly programmer insists on breaking law and order :-) So for example you could use it to enforce your team members to never ever use System.out.println() or never ever do UI logic from within DAO classes. Stuff like that. Good idea for big teams. Not a really useful thing for teams of rock star developers. It's interesting that turning on/off aspect "libraries" is just a matter of including/excluding the jars from the runtime of the application.

Compared to Spring/AspectWerks/others, I think AspectJ's approach is more refactoring-oriented than lets-code-some-aspects-from-the-begining of Spring and the gang. It's much easier to refactor existing code to an aspect in Eclipse+AspectJ than say in Spring. Of course Eclipse's built-in AspectJ support for such refactoring plays a central role in this "overall approach" to coding. The syntax is ugly but powerful. And I can't imagine how Tiger's metadata stuff could make it more bearable, because I think at least for pointcut declaration is doesn't make much sense and doesn't improve anything in any way!

Btw I liked the analogy Adrian made from pointcuts to commentary of a football match :-) And I liked his English accent too, classy :-) And according to him the holy trio of software at the moment are: AOP, DI (Dependency Injection) and my good old friend Annotations.

The JDK 1.5 talk was an overview of the new language stuff of the Tiger release, plus some recommendations on when to use and not to use some of those stuff. Joshua and Neal are fantastic speakers, specially when they are pair-presenting on stage! I used the 30 minutes coffee break to talk to Joshua about the annotation stuff, and although he was quite visibly exhausted but he answered my questions in great detail and accuracy. I guess now I understand some of those compromises made by JSR-175 expert group. They make sense. And some of those are obviously postponed to later releases. I expressed my concerns about some those compromises/limitations in a previous blog entry. Josh told me about a new tool in JDK 1.5 called apt. I think turning apt into a JSR is a very good idea. We could then hook our "annotation validation" stuff to it and have a complete solution for validating annotations. Validation was one of my concerns/complains about this spec. Another use would be "annotation overriding", to override some annotations with some others defined in an xml file for example (which I beleive EJB-3 group wants to do too).

Overall was a fantastic day! Though I didn't meet any of my friends today :-(

Ara.



Dependency Injection for Unit Tests

Ever wanted Dependency Injection for your unit tests? I got sick of doing context.getBean("thisDao") and context.getBean("thatDao") when testing my springified DAO classes.

What I wanted was something like this:

public class TestUserManagement extends BaseTestCase { private UserDao userDao = null; public void testSomething() throws Exception { userDao.createAdmin(); } }

Note that I don't new any UserDao. I expect the DI container to instantiate it for me.

As far as I know there's no DI container for junit, so I created one! But because I'm a lazy person and laziness is virtue I ended up with a very simple implementation. Here is the code:

public class BaseTestCase extends TestCase { protected ApplicationContext context; protected void setUp() throws Exception { context = new ClassPathXmlApplicationContext(new String[]{ "/testApplicationContext.xml", "/otherContext.xml"}); initDependencies(); } private void initDependencies() { Field[] fields = this.getClass().getFields(); for (int i = 0; i

In setUp() the Spring context is created and then dependencies are retrieved from the context and set. Instead of doing constructor or setter injection, I do field injection, with a trick. The code loops over all fields and if there's a bean defined with the name of the field, it sets it in the test class.

So in case of our sample test case above, making the field public like this:

public UserDao userDao = null;

And defining the dependency in the context xml file with the bean name "userDao" does the job. The field has to be public otherwise security manager complains about malicious code trying to hack our class by changing the field value :-)

So now I'm a happier guy because I don't have to write any bean lookup code in my test cases :-)

And in case you're wondering "what about mocks?", well mocks are ok, but you can't use mocks when you want to test the real code or when you're doing acceptance tests and such.

Ara.



The Next Election Won't Be Close

Read an interesting statistical analysis of the 2004 US elections by the Washington Monthly magazine and "Why the next election won't be close". Also according to the article John Kerry has a good chance of winning it too, and the other way around :-)

Ara.



Coming of Age

I usually keep a low tone on my work on this weblog, but this posting is different because I and My friend Armond (and a few others) are starting our own company!

So the idea is to offer offshore development, with excellent quality and excellent prices. Our team has a very successful track record, I think :-) Confluence, Java Open Source Programming book and XDoclet (admittedly long time ago) are a few of our works, the ones you've probably heard of. We've been involved in various kinds of projects, hardcore enterprise projects (Websphere, AS400, etc), web based thin clients, sophisticated Swing fat clients, mobile projects and so on!

So if you have a project and you're interested email me at ara_e_w at yahoo dot com.

Ara.



Kitchen-phobia

J2EE is like cooking at your own kitchen. Delicious but you have to learn cooking :-)

.Net is like buying fast food. Fast in, fast out, but is it healthy? Do you know what you just ate? Where's the sauce? :-)

Hope you don't have kitchen-phobia ;-)

Ara.



The Most Descriptive Error Message Ever

Hereby I announce the winner of the most descriptive error message competition (encountered by Farzad):

"[ERROR] java.sql.SQLException: [PWS0001] Function did not complete successfully. Cause . . . . . : An error occurred while running the function which caused the function to end before it could be completed. Recovery . . . : Determine what caused the requested function to fail, correct the problem, and run the function again."

Reported by WebSphere on AS400 from a DB2 database. Viva IBM!

Ara.



Pair Programming and Loners

I find this post by Kathy Sierra on Pair Programming and loners very interesting.

Being alone simply gives you the time to reorganize your thought. Pair Programming on the other hand achieves the same goal by exchanging thoughts among partners. I feel like doing the loner's way sometimes (most of the time?) and sometimes just by pairing with a colleague. People are different, they are in different moods, they organize their minds quite differently....

Related to this issue, I would really like to see a study on differences between male and female programmers 'cause I've seen some big differences on how each group analyze information. Does anyone know of such a study?

Ara.



Newsfeed display by CaRP

Arts:
See Arts in Open Directory

Return to News Feeds Home Page
My Sites