5
\$\begingroup\$

Being given the following code I wonder how can I make it better, without using variable valid, yet check ALL the cases and if at least one of them matches set the returned value to False

  def validate_ticketer_issue(self, ticketer_issue):
      """
      Validate ticket system issue for common rules
      :param ticketer_issue: (str) ticket system issue key
      :return: (bool) True if issue is successfuly validated
      """
      valid = True
      issues_not_allowed = ('Pool', 'Document', 'Declaration', 'Restriction', 'Pre-Check')
      issues_not_allowed_inprod = ('Analysis', 'Test')

      try:
          issue = self.service.issue(ticketer_issue)
          if len(issue.fields.versions) == 0:  # Affects Version/s
              valid = False
              print('{}: Affects Version/s field is empty'.format(ticketer_issue))

          if issue.fields.issuetype.name in issues_not_allowed:
              valid = False
              print('{}: According to working model commit is not'                        
                    ' allowed to the ticket system issue types'
                    '{}'.format(issue['key'], issues_not_allowed))

          if issue.fields.issuetype.name in issues_not_allowed_inprod:
              valid = False                             
              print('{}: According to working model commit' 
                    ' to the ticket system {} issue types'
                    ' is not allowed in production code'.format(
                        issues_not_allowed_inprod, issue['key']))

          if issue.fields.issuetype.name == 'Fault':
              if not self.validate_fault_issue_summary(issue):
                  valid = False

      except ticketer.exceptions.TicketerError:
          valid = False
          print('{}: issue does NOT exist in the ticket system!'.format(ticketer_issue))

      if valid:
          print('{}: OK'.format(ticketer_issue))

      return valid
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Hi, and welcome to Code Review. Could you please add some context to help us understand your code. What problem is it trying to solve? How does it currently solve it? What constraints, if any, are you under? What would you consider "Better" (Faster, Cleaner, Shorter, Clearer etc.)? \$\endgroup\$
    – Kaz
    Commented Feb 10, 2017 at 10:37
  • \$\begingroup\$ @Kaz I mean cleaner solution would be without using flag variable 'valid', but still checking all the cases and printing as the result the proper message and in the end would return False if any of the cases would match or the Exception would be caught. Just wonder is it anyway to make it better. \$\endgroup\$
    – mastier
    Commented Feb 10, 2017 at 10:39
  • \$\begingroup\$ Hi, As I see it, the error messages are not mutually exclusive. One call can produce many error messages. Is that right? \$\endgroup\$
    – daragua
    Commented Feb 10, 2017 at 10:44
  • \$\begingroup\$ Indeed, I want them all to be printed \$\endgroup\$
    – mastier
    Commented Feb 10, 2017 at 11:36
  • \$\begingroup\$ On top of the context asked by @Kaz about the whats and whys of your code, can you also provide context about the hows by adding more code (what kind of object is issue, what does the various methods you are calling do, etc.) \$\endgroup\$ Commented Feb 10, 2017 at 12:20

1 Answer 1

5
\$\begingroup\$

The code you show is explicit about what it does, easy to read and understand. I suppose you find the usage of the valid flag error-prone in case the logic gets deeper nested, or any sort of refactoring that might forget about that flag?

When you encounter an error, you are actually doing two things : setting the valid flag to False and printing an error. One simplification would be to make a list of error messages (no valid variable), and push error messages on it. You don't print anymore, and the list of errors becomes the valid flag.

def validation_function(item):
    errors = []

    try:
        if not test1:
            errors.append("Error on test 1 for item")
        if not test2:
            errors.append("Error on test 2 for item")
    except SomeException as e:
            errors.append("Exception on whatever for item")

    # change the return type to list of errors
    # reporting errors is responsability of caller.
    return errors

You could raise an exception from inside the validation_function but I see it as a low level validation routine which collects errors and returns them. The caller is responsible for handling (raise, print) the returned errors if any.

Also, your validate_ticketer_issue is actually harmonizing errors : it leads its own set of tests but also catches exceptions raised by functions it calls and translates that to a boolean + error messages. The above example preserves that behaviour which is all right as long as you are very specific about the exceptions you catch.

Finally you could see the function as a bunch of predicates with specific error messages run one after the other.

# List of predicates and messages. Predicates must have a 
# harmonized api. Note: I'm using lambdas here but regular 
# functions will work too.
PREDICATES = [
    (lambda item: some_logic_here, "Error on test 1 for item"),
    (lambda item: some_other_logic_here, "Error on test 2 for item")
]

def validation_function(item):
    errors = []

    try:
        for predicate, msg in PREDICATES:
            if not predicate(item):
                # here you have to harmonize you messages
                # so that they can be formatted using the
                # same argument.
                errors.append(msg.format(item=item))
    except SomeException as e:
            errors.append("Exception on whatever for item")

    # change the return type to list of errors
    # reporting errors is responsability of caller.
    return errors

This example might seem more scalable but for a small amount of predicates, it is probably overkill as understanding the code is not obvious, logic lives in other places, and some work has to be done to harmonize predicates and their messages.

These are the few ideas I have to improve the code so that it doesn't rely on the valid flag. The second step makes it perhaps more scalable at the expense of readability.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.