3

I am still an beginner/intermediate programmer at best so apologies if I misuse terminology.

I work with SCADA software for my job, specifically Ignition by Inductive Automation. It's made for rapid application development, and all you really need to know is that it's built upon java, so what I end up using in practice is jython 2.7.

The application I work on strictly a database CRUD application for tracking office paperwork, invoices, sales orders, etc. I realized after a while the structure of each of these forms is essentially the same set of steps, with slightly different implementations at different steps.

For creating a new invoice for instance, I get some data off the screen, I validate any values that might not be good (ex: a age that is negative) and throw an error if there is one, if not, I sanitize the data if needed (sometimes due to poor GUI design ie changing a string date to a date object), and then I run a named query with the parameters to make an insert inside of a db transaction (and running any other database queries that are related to the invoice inside the same db transaction in case something goes wrong I rollback). If everything was successful, I log a message to a history thread for the object, or if not, I log the error to a server and tell the user something went wrong.

Now some things, this abstract class (think this is the right term) Form takes care of a few things behind the scenes - it automatically figures out where the subclasses named queries should be - for invoices it will look in a folder called forms/invoices for named queries called create, update, and delete for the appropriate actions. The Form class also has a generic way of running subclasses create/update/delete statements within a the database transaction, and I keep all error parsing inside the abstract class Form so that every subclasses handles db errors or validation errors the same way.

As an example, for creating a new record, this is what my Form class looks like

def create(self, newData):
    self.logger.info("running create")
    # Have to reset this here in case of multiple creates, don't want a false positive on the second item
    self.resetResults()
    self.newData = newData
    self.modifyNew()
    self.validateNew()
    self.sanitizeNew()
    self.inputDefaultValues()
    # Database transaction/error catching - but only on the top level
    if not self.middleOfTransaction:
        self.tryAndFailDBOperationGracefully("inserting")
    else:
        self._create()
    # TODO - decide - if we should even update the UI if there's an error after the db stuff.  Leave for now
    self.updateUIAfterInsert()

Some of these things the user needs to implement if they want - modifyNew, validateNew, sanitizeNew, and updateUIAfterInsert are all pass in the Form class and can be skipped if not used but otherwise should be implemented if you want them to run. Other things like resetResults, inputDefaultValues, tryAndFailDBOperationGraceflly, _create are all things subclasses should not overwrite.

So how can I document something like this, what is the right way to inform someone via a UML diagram that if you want to use my Form class, here's how it works, and here's what you need to do on your end to subclass it properly? Also related - is this Form object an abstract class or is it a Interface? I am confused about the difference.

Christophe
  • 81,699

3 Answers3

3

UML class diagrams answer three questions well:

  • What knows about what? (Note the lines)
  • What are these things called? (Note the class names)
  • How do you talk to these things? (Note the method signatures)

If you’re trying to explain something that isn’t the answer to those questions then use something else to explain.

Note: Abstract classes and Interfaces are different mainly in those languages that use Interface as a keyword. Typically this means they didn’t implement multiple inheritance correctly the first time around and hacked it into their language later by adding the Interface keyword.

A lower case i, “interface”, simply describes how you can talk to it. Completely ignores how it works.

candied_orange
  • 119,268
1

Documentation for programmers

First off, as you said you want to provide documentation for developers using this class. There is some discussion about documenting/commenting code, that you can find eg. here:


Step towards self-documenting code

One thing that goes in the direction of self-documenting code is the following: You mentioned a set of methods that should not be overridden - resetResults, inputDefaultValues, etc. If that always should hold, you could change the methods to follow the Python convention for private access method: in prefixing them with two underscores, having then __resetResults, __inputDefaultValues, and so on.


You have applied a Design Pattern (which includes existing UML diagrams)

Some additional context to your approach: With the Form class you are in fact following one of the classic design patterns by Gamma et al: the Template Method pattern.

So, I would not create any additional UML diagram, but instead just reference the pattern.

The documentation itself, about the available methods, etc., could just by added to the Form class itself as a Python docstring.


Additional info

For completeness, in Python since version 2.6 there is in fact a sort of abstract method support with the ABC module. Though that could be from a complexity standpoint counterproductive for your use case.

To have sort of a mandatory abstract method in Python you can always just raise a NonImplementedError in the base class method.

Cwt
  • 111
1

Communicate the big picture

To communicate the big picture about a set of classes that are supposed to work together, you may consider the following:

  • An UML class diagram (structure), to explain the involved classes, their responsibilities, and their relationships. In your case, you would typically document the operations (UML term for methods) that each class has to provide for your interaction to work.
  • An UML sequence diagram (dynamics), to explain how involved objects of these classes interact by creating each other, and communicating (i.e. calling their operations). Keep diagrams simple, you may make several such diagram, each explaining a specific scenario.

These two simple diagrams help to grasp at a glance the classes involved and how they work together.

Improve your customer/developer's experience

It's not graphical overview vs. self-explanatory code: it's both together. The diagrams are the map that tells where to look for the treasure. The users can then navigate quickly to the related parts with their to find more information. But without the map, the customers might take a lot longer to figure out how all the parts fit together.

To maximise the experience:

  • Keep it lean: show only on the key elements of your design. Don't put all attributes and methods; this would only lead to unreadable diagrams that might frighten away your audience ;-)
  • Ideally, you'd use the the diagrams together with a short explanatory text on the landing page of your documentation,
  • You may provide also an API documentation generated automatically from your comments.

If you're using python, you can give a quick trial at PyReverse (it's part of Pylint, a tool you should consider if you're making mission critical SCADA systems) : it generates automatically some UML class diagrams. But usually, for a design overview, you'll be as fast drawing it from scratch.

Abstract class or not?

Form is a class since it provides an implementation for some methods. It's probably not an abstract class by the book, if it can be instantiated.

Nevertheless, nothing prevents you to show it as abstract on the diagram, to underline that a subclass must be created to make it work. Personally, I'd show is as a normal class and would show one subclass in the diagram that shows the operation that should/could be overridden.

Christophe
  • 81,699