11 Replies Latest reply on Dec 4, 2009 3:39 PM by ropalka

    Embedded API Design Problems

    ropalka

      Please take it as feedback and not hassling ;)

      INCONSISTENT METHOD NAMES

      * ISSUE 1: Many Embedded project classes/intefaces follow BEAN naming convention but there are some exceptions.

      * RULE 1: API should be consistent. Once you're following BEAN naming convention, whole API must apply to this rule.

      Example:

      package org.jboss.bootstrap.api.as.config;
      
      public interface JBossASBasedServerConfig<T extends JBossASBasedServerConfig<T>> extends ServerConfig<T>
      {
       ...
       String getBindAddress();
       T bindAddress(String bindAddress); // this is WRONG, see RULE 1
       ...
      }
      
      package org.jboss.bootstrap.api.server;
      
      public interface Server<K extends Server<K, T>, T extends ServerConfig<T>>
      {
       ...
       T getConfiguration();
       void setConfiguration(T config); // this is CORRECT
       ...
      }
      


      SUGGESTION: The best practice is to follow bean naming convention for setter/getter methods.

      DO NOT DECLARE TO THROW RUNTIME EXCEPTIONS

      * ISSUE 2: Many embedded project classes/interfaces declare to throw RuntimeExceptions, but only for some methods

      * RULE 2.1: Declare exception to be thrown if its abstraction is consistent with class/API (correct: FileOutputStream.write(byte[]) throws IOException)
      * RULE 2.2: Declare exception to be thrown if it's expected (wrong: FileOutputStream.write(byte[]) throws ArrayIndexOutOfBoundsException)
      * RULE 2.3: Once API designer decides to declare to throw exception this decision must be consistent for whole API.
      * RULE 2.4: Throwing checked exceptions force users of the API to catch or propagate them, consider this when designing API.
      * RULE 2.5: Runtime exceptions indicate programmer errors. The best practice is to don't declare them in method signature.

      Example:

      package org.jboss.bootstrap.api.as.config;
      
      public interface JBossASBasedServerConfig<T extends JBossASBasedServerConfig<T>> extends ServerConfig<T>
      {
       ...
       URL getServerLibLocation();
       T serverLibLocation(URL serverLibLocation); // this is WRONG: see RULE 2.3
       T serverLibLocation(String serverLibLocation) throws IllegalArgumentException; // this is WRONG - see RULE 2.1, RULE 2.2, RULE 2.4 and RULE 2.5
       ...
      }
      
      


      INCONSISTENT ENUM VALUES

      * ISSUE 3: There are inconsistent enum values in embedded API.

      * RULE 3: Always define consistent enums/constants to don't confuse users.

      Example:

      package org.jboss.bootstrap.api.lifecycle;
      
      public enum LifecycleState
      {
       INSTANCIATED,
       PRE_INIT, // this is WRONG, should be INITIALIZING to be consistent with other constants
       INITIALIZED,
       IDLE,
       STARTING,
       STARTED,
       STOPPING,
       STOPPED
      }
      
      


      INCONSISTENT API ABSTRACTION

      * ISSUE 4: There are low level exceptions (including runtime exceptions) declared to be thrown (checked exceptions) in some methods, in others not.

      * RULE 4: Instead of throwing low level exception API designer should create exception that is consistent with API abstraction.
      In many cases such main abstraction exception don't need to be specified in throws clausule (just throw it).

      Example:

      package org.jboss.bootstrap.api.factory;
      
      public class ServerFactory
      {
       ...
       public static Server<?, ?> createServer(final String implClassName)
       throws IllegalArgumentException, InstantiationException; // this is WRONG, see RULE 4
      
       public static Server<?, ?> createServer(final String implClassName, final ClassLoader cl)
       throws IllegalArgumentException, InstantiationException // this is WRONG, see RULE 4
       ...
      }
      
      public class ServerFactory
      {
       ...
       public static Server<?, ?> createServer(final String implClassName) // this is CORRECT
       {
       ...
       catch (InstantiationException ie)
       {
       throws BootstrapException(ie);
       }
       ...
       }
      
       public static Server<?, ?> createServer(final String implClassName, final ClassLoader cl) // this is CORRECT
       {
       ...
       catch (IllegalArgumentException ie)
       {
       throws BootstrapException(ie);
       }
       ...
       }
      }
      

      SUGGESTION: Create abstract exception that best fulfills the API abstraction and consider if it will be thrown from the API or not.
      * should be declared to be thrown from API if it is not assignable from java.lang.RuntimeException and user have to deal with it
      * shouldn't be declared to be thrown from API if it is assignable from java.lang.RuntimeException and represents programmer error

      Example:
      * BootstrapException extends RuntimeException // defining API abstraction exception - it's Throwable getCause() returns the original exception that caused the failure
      * LifecycleEventException extends BootstrapException // all exceptions defined in embedded API should extend main abstraction exception

      OTHER CODE INCONSISTENCIES

      * ISSUE 5: There are many inconsistencies in embedded API cross different components.

      * RULE 5: Avoid API inconsistencies. They usually indicate wrong design or decomposition of the problem.

      Example:

      package org.jboss.bootstrap.api.mc.server;
      
      public interface MCBasedServer
      <
       K extends org.jboss.bootstrap.api.server.Server<K, T>,
       T extends org.jboss.bootstrap.api.config.ServerConfig<T>
      >
      extends org.jboss.bootstrap.api.server.Server<K, T>
      {
       org.jboss.kernel.Kernel getKernel();
      }
      
      but
      
      package org.jboss.bootstrap.api.as.server;
      
      public interface JBossASBasedServer
      <
       K extends JBossASBasedServer<K, T>, // this is WRONG: shouldn't it be K extends org.jboss.bootstrap.api.server.Server<K, T> ?
       T extends org.jboss.bootstrap.api.as.config.JBossASBasedServerConfig<T> // this is WRONG: should be T extends org.jboss.bootstrap.api.config.ServerConfig<T> ?
      >
      extends org.jboss.bootstrap.api.server.Server<K, T>
      {
      }


        • 1. Re: Embedded API Design Problems
          ropalka

          The same problems are in ShrinkWrap API.

          • 2. Re: Embedded API Design Problems
            alrubinger

            Thanks for the feedback, Richard. We're at Alpha-level to encourage this kind of discussion.

            "richard.opalka@jboss.com" wrote:
            INCONSISTENT METHOD NAMES
            API should be consistent. Once you're following BEAN naming convention, whole API must apply to this rule.


            I'm not strictly following bean conventions. For the method chaining signatures, I considered Fowler's note on command query separation as detailed in http://martinfowler.com/dslwip/MethodChaining.html.

            "richard.opalka@jboss.com" wrote:
            DO NOT DECLARE TO THROW RUNTIME EXCEPTIONS


            I'm a big fan of declared unchecked exceptions; they more completely document the API. I don't care if the exception thrown is in a different package (eg Rule 2.2 "FileOutputStream.write(byte[]) throws ArrayIndexOutOfBoundsException"). Rule 2.3, consistency, is a design goal and any places that do not follow this need to be ironed out; which did you mean in particular? In order to act upon rule 2.4, forcing the user to deal w/ checked exceptions, I'll need some examples you believe need attention. I don't agree with Rule 2.5.

            From where are you deriving these rules BTW?

            "richard.opalka@jboss.com" wrote:
            INCONSISTENT ENUM VALUES


            "PRE_INIT" seemed more descriptive to me; it's fired *before* initialization is called. Your suggestion to "INITIALIZING" isn't accurate as the server is not in the process of actually initializing; perhaps we should go to "PRE_INITILIZATION" or similar?

            "richard.opalka@jboss.com" wrote:
            INCONSISTENT API ABSTRACTION

            Instead of throwing low level exception API designer should create exception that is consistent with API abstraction.
            In many cases such main abstraction exception don't need to be specified in throws clausule (just throw it).


            I don't consider Java Platform exceptions to be unnecessarily low-level; in fact Bloch encourages their use in validating preconditions/postconditions (I'd make a reference here but don't have my book w/ me). These are well-known APIs which programmers are likely to know already. Why wrap these in some Embedded-specific API? IllegalArgumentException and InstanciationException means exactly that.

            "richard.opalka@jboss.com" wrote:
            OTHER CODE INCONSISTENCIES


            I don't see how the generics issue you point out is an inconsistency. Also "other inconsistencies" doesn't help find action items. :)

            S,
            ALR



            • 3. Re: Embedded API Design Problems
              ropalka

               

              "ALRubinger" wrote:

              Thanks for the feedback, Richard. We're at Alpha-level to encourage this kind of discussion.

              Do you know golden rule: Design your API properly since beginning. Later there will be no will to rewrite it?

              "ALRubinger" wrote:

              I'm not strictly following bean conventions. For the method chaining signatures, I considered Fowler's note on command query separation as detailed in http://martinfowler.com/dslwip/MethodChaining.html.

              Thanks, I will have a look. Maybe I'll change my mind ;)

              "ALRubinger" wrote:

              I'm a big fan of declared unchecked exceptions; they more completely document the API.

              If you mean declaring it using @throws clause, I'm fine with you otherwise let me disagree.
              See RULE 41 from Effective Java that says:
              In case programmer that is using your API cannot handle thrown exception,
              it's more proper to declare it as unchecked exception.

              "ALRubinger" wrote:

              I don't care if the exception thrown is in a different package (eg Rule 2.2 "FileOutputStream.write(byte[]) throws ArrayIndexOutOfBoundsException"). Rule 2.3, consistency, is a design goal and any places that do not follow this need to be ironed out; which did you mean in particular?

              See RULE 43 (Exception translation) from Effective Java.

              "ALRubinger" wrote:

              In order to act upon rule 2.4, forcing the user to deal w/ checked exceptions, I'll need some examples you believe need attention. I don't agree with Rule 2.5.

              What about RULE 40 from Effective Java ;)
              (Regarding examples that need your attention, I provided one. Do you think it's not relevant one?)

              "ALRubinger" wrote:

              From where are you deriving these rules BTW?

              From the same source like you Effective Java (my notes)

              "ALRubinger" wrote:

              I don't consider Java Platform exceptions to be unnecessarily low-level; in fact Bloch encourages their use in validating preconditions/postconditions (I'd make a reference here but don't have my book w/ me). These are well-known APIs which programmers are likely to know already. Why wrap these in some Embedded-specific API? IllegalArgumentException and InstanciationException means exactly that.

              This is again RULE 43 (Exception translation) from Effective Java.

              "ALRubinger" wrote:

              I don't see how the generics issue you point out is an inconsistency. Also "other inconsistencies" doesn't help find action items. :)

              I provided you examples of two classes from two different packages implementing the same feature.
              Still can't see it? ;)

              • 4. Re: Embedded API Design Problems
                dmlloyd

                 

                "richard.opalka@jboss.com" wrote:
                Please take it as feedback and not hassling ;)

                INCONSISTENT METHOD NAMES

                * ISSUE 1: Many Embedded project classes/intefaces follow BEAN naming convention but there are some exceptions.

                * RULE 1: API should be consistent. Once you're following BEAN naming convention, whole API must apply to this rule.


                I buy this, even with builder-pattern-like things. Mutators should have "set" in front of them, even if they return "this" for use in method call chaining.

                BTW, rather than using a generic type paramter representing "this class", you should just leverage covariant return types and have each subtype override the return type with its more specific type. With the generic parameter this is being done anyway; the difference is, now users often have to deal with the onerous and obnoxious "this" type parameter. Any time you have "Foo<T extends Foo<T>>" that should be a warning to proceed with caution.

                "richard.opalka@jboss.com" wrote:

                DO NOT DECLARE TO THROW RUNTIME EXCEPTIONS
                ...
                * RULE 2.5: Runtime exceptions indicate programmer errors. The best practice is to don't declare them in method signature.


                Couldn't disagree more. Putting all possible/expected unchecked exception types in the throws clause gives users a sense of what to expect; also it can hint to IDEs that you might want to add a catch clause for it. And it does no real harm.

                "richard.opalka@jboss.com" wrote:

                * shouldn't be declared to be thrown from API if it is assignable from java.lang.RuntimeException and represents programmer error


                Again, disagree.


                • 5. Re: Embedded API Design Problems
                  alrubinger

                   

                  "richard.opalka@jboss.com" wrote:
                  Do you know golden rule: Design your API properly since beginning. Later there will be no will to rewrite it?


                  Absolutely. Which is precisely why I engaged the community with pointers on the jboss-development list and a "Getting Started" Wiki months before even the first release. Part of this is "planning for change", so "Alpha" denotes we can make these types of adjustments before locking down.

                  I'll look into the rest later on, but I doubt you'll be able to convince me that it's a bad idea to declare an unchecked exception . No bad can come of it, and it's self documenting.

                  Actually there's a few generics issues I have patches for in jboss-bootstrap anyway, but didn't merge them in yet as to not slow down this release.

                  S,
                  ALR

                  • 6. Re: Embedded API Design Problems
                    jason.greene

                    I disagree with the server factory example in Rule 4. IllegalArgumentException is the appropriate exception to be thrown since, if you look at the code, it pertains to the parameters that are passed to the method. It is true that the exceptions is thrown by a different class, but this class is the implementation of the API. So as long as the behavior is consistent to the API then this is fine. There is definitely no need to transfrom IllegalArgumentException into a checked exception, as it is a pure runtime error that indicates misuse of the API. In other words, it is more of an assertion than it is a true exceptional condition.

                    I am indifferent on putting RuntimeExceptions in the throws clause. At the end of the day, it doesn't really matter, and usage of the javadoc @throws is just as effective IMO.

                    I do agree with the API consistency argument, however I disagree with using get/set with method chaining. If you are doing a DSL style method chain, you don't need get/set, it's just extra verbosity. Get is logically the no-arg version, and Set is logically the version which takes arguments. In general I think Java developers took the JavaBean recomendations WAY too far. The whole reason JavaBeans required get & set notation was so that an automated tool could figure out what the "properties" are. In other words, JavaBeans are a big hack.

                    Also, as a more general comment, Bloch's advice is just that, advice. API design is somewhat subjective, so we shouldn't treat them as hard and fast rules.

                    • 7. Re: Embedded API Design Problems
                      ropalka

                       

                      "david.lloyd@jboss.com" wrote:

                      "richard.opalka@jboss.com" wrote:

                      DO NOT DECLARE TO THROW RUNTIME EXCEPTIONS
                      ...
                      * RULE 2.5: Runtime exceptions indicate programmer errors. The best practice is to don't declare them in method signature.


                      Couldn't disagree more. Putting all possible/expected unchecked exception types in the throws clause gives users a sense of what to expect; also it can hint to IDEs that you might want to add a catch clause for it. And it does no real harm.

                      "richard.opalka@jboss.com" wrote:

                      * shouldn't be declared to be thrown from API if it is assignable from java.lang.RuntimeException and represents programmer error


                      Again, disagree.


                      Let me disagree ;)
                      * Runtime exceptions represent programmer errors
                      * Checked exceptions (declared in throws clause) represent special cases user (not programmer) should deal with/know about

                      Here's one example from String.class:

                      The following public String(char value[], int offset, int count) constructor throws StringIndexOutOfBoundsException runtime exception.

                      If I'd change its signature like you suggest, i.e.:
                      public String(char value[], int offset, int count) throws StringIndexOutOfBoundsException
                      the working with such API would look like:

                      char[] data = ...;
                       String s = null;
                       try
                       {
                       s = new String(data, 0, 5);
                       }
                       catch (StringIndexOutOfBoundsException e) // I'm forced to catch the exception or rethrow it
                       {
                       log.error("Damn! Man fix it, then start application again", e);
                       System.exit(1);
                       }
                      


                      IMO runtime exceptions cannot be part of the signatures,
                      because they represent my programming errors.
                      The only way I can fix them is to shutdown application, then
                      fix it and again start the application.
                      I'd end with hundreeds/thousands application exit points :(


                      • 8. Re: Embedded API Design Problems
                        ropalka

                         

                        "jason.greene@jboss.com" wrote:
                        I disagree with the server factory example in Rule 4. IllegalArgumentException is the appropriate exception to be thrown

                        Of course you're right Jason, the correct code should be:

                        package org.jboss.bootstrap.api.factory;
                        
                        public class ServerFactory
                        {
                         ...
                         public static Server<?, ?> createServer(final String implClassName)
                         throws IllegalArgumentException, InstantiationException; // this is WRONG, see RULE 4
                        
                         public static Server<?, ?> createServer(final String implClassName, final ClassLoader cl)
                         throws IllegalArgumentException, InstantiationException // this is WRONG, see RULE 4
                         ...
                        }
                        
                        public class ServerFactory
                        {
                         ...
                         public static Server<?, ?> createServer(final String implClassName) // this is CORRECT
                         {
                         ...
                         catch (InstantiationException ie)
                         {
                         throws BootstrapException(ie);
                         }
                         ...
                         }
                        
                         public static Server<?, ?> createServer(final String implClassName, final ClassLoader cl) // this is CORRECT
                         {
                         ...
                         throw new IllegalArgumentException();
                         ...
                         }
                         ...
                        }
                        


                        • 9. Re: Embedded API Design Problems
                          ropalka

                           

                          "jason.greene@jboss.com" wrote:

                          I do agree with the API consistency argument, however I disagree with using get/set with method chaining. If you are doing a DSL style method chain, you don't need get/set, it's just extra verbosity. Get is logically the no-arg version, and Set is logically the version which takes arguments. In general I think Java developers took the JavaBean recomendations WAY too far. The whole reason JavaBeans required get & set notation was so that an automated tool could figure out what the "properties" are. In other words, JavaBeans are a big hack.

                          I agree get/set vs. DSL style is subjective. To clarify my stay I'm just complaining about API consistency.

                          The following two classes are some kind of To be, or not to be Hamlet dilemma in a way:
                          package org.jboss.bootstrap.api.as.config;
                          
                          public interface JBossASBasedServerConfig<T extends JBossASBasedServerConfig<T>> extends ServerConfig<T>
                          {
                           ...
                           String getBindAddress();
                           T bindAddress(String bindAddress); // I'm using set/get style in API design?
                           ...
                          }
                          
                          package org.jboss.bootstrap.api.server;
                          
                          public interface Server<K extends Server<K, T>, T extends ServerConfig<T>>
                          {
                           ...
                           T getConfiguration();
                           void setConfiguration(T config); // Or I'm using DSL style in API design?
                           ...
                          }


                          "jason.greene@jboss.com" wrote:

                          Also, as a more general comment, Bloch's advice is just that, advice. API design is somewhat subjective, so we shouldn't treat them as hard and fast rules.

                          Agreed.

                          • 10. Re: Embedded API Design Problems
                            dmlloyd

                             

                            "richard.opalka@jboss.com" wrote:
                            "jason.greene@jboss.com" wrote:
                            I disagree with the server factory example in Rule 4. IllegalArgumentException is the appropriate exception to be thrown

                            Of course you're right Jason, the correct code should be:


                            Again, I can't agree with you. You're abiding by a set of subjective criteria as though they were laws. :-) Rather than saying "this is incorrect" what you really mean is, "this is not how I like it, but that's just my opinion".

                            The true test is, is the API nice to use? Does it make sense logically? If these two are true, then the API is probably just fine. Everyone has their own opinion about how APIs should look and feel, and I can tell you now that there will never be a time where everyone converges on what your (or my) idea of "right" is.

                            So I think a better measurement is, rather than saying "X, Y and Z violate my rules for APIs", look at what code which actually USES the API might look like and ask yourself, "can this cause a surprise for users? Is it unintuitive? Is there a way this could be made better with that in mind?" If the answer is something like "Yes, the user will need 5 probably-identical catch clauses every time they call method X, and they're all very unlikely!" then you can objectively say it's a problem. If the answer is "I don't like ____" then that's just your opinion and you should advertise it thus.

                            • 11. Re: Embedded API Design Problems
                              ropalka

                              You expressed what I wanted to say more properly David, I used wrong wording.