Showing posts with label code-standards. Show all posts
Showing posts with label code-standards. Show all posts

Tuesday, April 18, 2017

PEP-8 vs. My Coding Standards

Before diving into the markup module of the idic framework, I wanted to take a quick look at coding standards from an "official" perspective/standpoint. Specifically Python's PEP-8 standards, since they've been a topic of some interest for me recently. I'm jumping the gun a bit — as I'm writing this, most of the markup module is actually mostly complete, but I haven't posted any of it, but bear with me, please...

Today's post ended up being something that felt better as a global page in the blog (I'll likely refer to it from time to time elsewhere/later), so I've copied it as such: PEP-8 vs. My Coding Standards. As I update my thoughts on this topic, that page will get updated…

If you've been reading the code I've been sharing here, and are aware of the PEP-8 standard, you may have noticed that I'm not adhering to all of the PEP-8 guidelines. A few of them I've consciously chosen to not abide by, for various reasons that I feel fall into its A Foolish Consistency is the Hobgoblin of Little Minds criteria. A handful (I think) are hold-overs from working in projects that had very specific (sometimes rigid) standards for variable names. Those habits were formed over the course of years. In one case that helped form those habits, there was an even expectation that the build-process for the associated codebase would actively check for compliance with those style-guidelines, and if they weren't followed, the build would fail. While that helped reinforce those guidelines, as well as they habits they formed, I think that took things just a bit too far.

That same hobgoblins section raises some thoughts for me about application of rules around coding standards (naming-conventions in particular). Setting aside my admitted habits, I tend to think along lines that I thought were best expressed by a favorite author as:

Look, that's why there's rules, understand? So that you think before you break 'em.
— Terry Pratchett, from Thief of Time

PEP-8 itself sets some priorities that I feel fall pretty much in line with that:
Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important. (emphasis mine).
Yes, there are rules. Yes, they are there for a reason. But they aren't set in stone, and there may be valid reasons to bend or even break them.

Still, style-guidelines are valuable, particularly in an environment where more than one person is going to be in the position of reading the code. Since I know I have some habits that would be considered bad by PEP-8 standards, I'm going to run down the list, noting which ones I've kept, which ones I've dropped (and why), and which ones I should probably adopt that I missed somewhere along the line.

Code Layout

Many of the PEP-8 standards about code layout I follow to the best of my ability to do so:

  • Indentation of 4 spaces per level Yes
  • Using spaces for indentation, not tabs Yes
  • Line length of 79 characters max: Usually
  • Line-lengths of 72 characters for long/multiline strings: Probably not — but they will conform to the more general 79-character line-length
A few I actively try to adhere to, but likely with mixed results:
  • Line-breaks before operators
  • Blank-line usage
  • Imports (though I think I'm pretty consistently adhering, I haven't gone and looked)
  • Wildcard imports (probably more consistently not adhering, simply because the IDE I'm using isn't smart enough to add individual import-items in to the code, even though it is smart enough to recognize they exist)

The one remaining item in the Code Layout standards (Module-level "dunder" names), I know I'm not adhering to. They show up in my module- and package-header template files:

#-----------------------------------#
# File metadata                     #
#-----------------------------------#
__author__  =       'Brian D. Allbee'
__version__ =       '0.1'
__copyright__ =     'Copyright 2017, Some rights reserved by Brian D. Allbee'
__license__ =       ('This work is licensed under a Creative Commons '
                      'Attribution-ShareAlike 4.0 International License. '
                      '(http://creativecommons.org/licenses/by-sa/4.0/)')
__credits__ =       ['Brian D. Allbee']
__maintainer__ =    'Brian D. Allbee'
__email__ =         'brian.allbee+module_name@gmail.com'
__status__ =        'Development'
I don't know whether I'll bother with changing those, frankly. On one hand, I find that I can more easily read these they way they are right now, though so far I haven't had much need to read them. On the other hand... Well, I don't really have an on the other hand scenario that I can point to.

String Quotes

I suspect that most developers working in Python will have naturally picked up the guidelines here — Using one type of quote (whether single or double) is probably habitual, and switching to the other to avoid having to escape the preferred quote is something that's pretty easy, provided that you know it's going to happen when you start typing.

Me, I tend to use single-quotes out of habit, and I stick to that pretty well. In the code I've shown so far, there's one pattern where I suspect I break from that pretty consistently: The descriptions of arguments and such in my documentation decorators. That's usually going to happen because I don't know if I'll need to use single- or double-quotes within the description-string until I've written it, by which time it's almost always going to be faster to escape the one or two characters in the string (or, sometimes, to just type \ then and there) than to replace the characters at the ends of the string.

Whitespace in Expressions and Statements

Spaces inside (), [] and {} are going to be a pretty consistent offense (if such it is) on my part. I'm not likely to change this, I'm afraid, at least not soon, because while readability counts in general (PEP-20/The Zen of Python [execute import this in a Python session]), the simple truth of the matter is that right now my ability to read it counts a bit more than anyone else's, and I find it easier to read with those spaces. If things get to a point where anyone but me is actually using my code, I may set something up in the build-process to strip out those additional spaces, but I don't expect that to happen for a while.

Separate, but related: Odds are good that I've got several instances of whitespace at the ends of lines, if only because as I'm shuffling code around, I usually just don't think about removing any that sneak in.

Comments

Keeping comments current and relevant is something that I try to do, but I'd be very surprised if there weren't at least a few here and there where something changed but an associated comment didn't get changed accordingly. The longer a codebase is undergoing active development, the more likely that sort of disjoint is, I suspect.

Block-comments are usually going to be OK, I think — though there will, I'm sure, be chunks of comments that won't conform because they're items I need to differentiate quickly and easily. Usually that will be chunks of actual code, commented out because it's not needed for functionality reasons, but could be useful for testing or logging while code-changes are being made. In those cases, they'll usually be commented out at the beginning of the line, making it easier to differentiate between a real comment and some code that's been commented out but kept for some reason.

I try not to use inline comments, though there are probably a few here and there in some of my older code and projects. I doubt that there are many in the code here, if only because it's just not old enough for them to start accumulating yet.

Documentation Strings

May be moot, since I plan to use my documentation decorators in all my code, and they will generate results that are more consistent (and more verbose) by design.

Naming Conventions

If there is any single category of guidelines that my code is going to violate, this is going to be it, but there's a reason behind it. The target audience, such as there is one, for most of the code I'm expecting to write for this blog could be described pretty well as web application developers. To my thinking, that means there's a pretty good chance that those target-audience users will be familiar with JavaScript and its conventions. Some classes (particularly in the markup-generation parts of the framework) I actually want to mirror their JavaScript equivalents as closely as makes sense. JavaScript uses CapWords (or CamelCase) in its class-names, and mixedCase for object properties and methods (using the names of the styles as listed in PEP-8). In portions of the framework, then, that overlap that desire to mirror a JavaScript API equivalent, I'm using naming-conventions that will be more familiar to developers with JavaScript experience. That's a conscious choice on my part, and since I expect/hope that most of this code will be used by members of that web application development group, it has leaked out across to other sections of code that aren't as directly related...

Outside of those JavaScript-related cases:

  • Short, all-lower-case names for modules and packages — Yes;
  • CapWords for classes (which holds true in both contexts) — Yes;
  • Function and class-member names — No (but there's a reason)
Functions and class-members (whether methods or properties) that do not have a JavaScript equivalent will also use CapWords naming, because there will be cases where classes have JavaScript and non-JavaScript members alike, and I'd like to have some sort of readability-level hinting in those member-names indicating whether they're JavaScript equivalents or not. The recommended convention (lower_case_with_underscores) I find to be less readable in general, particularly in cases where those members exist in a class that also has JavaScript-equivalent members. It's not a great compromise, to be sure, but I think it's the best compromise under the circumstances.

Given that the framework is all one project, and that

Consistency within a project is more important.
that more or less mandates that all of the framework's codebase should at least prefer my non-standard, non-PEP-8-compliant naming conventions for the sake of consistency. Applications built on top of that framework may not share that preference, though, and shouldn't reflexively abide by them.

Programming Recommendations

PEP-8 also contains a pretty substantial list of Programming Recommendations. Many of them are moot for my efforts, at least so far — they relate to capabilities of Python that I'm simply not using (yet). Most of the rest I stick to pretty well, I think, though even in those cases, there may be additional relevant information or concerns:

  • Singleton comparison (e.g., x is not None vs. x != None): Probably moot, since I generally don't use None except in boolean-compatible ways
  • Deriving custom exceptions from Exception: Yes (to be honest, I wasn't aware that there was an underlying exception-class deeper than Exception, so I pretty much got lucky in this case)
  • Catching specific exceptions: Yes, though I know there are a few instances of bare except code that I'll need to track down and reconcile by specifying the base Exception
There are a couple of other areas that I'll need to take a closer look at:
  • Newer exception-binding (e.g., except Exception as error vs. except Exception, err). I hadn't seen this structure until I started this review, so I'll probably need to go looking for the older-style cases and correcting them.
  • Making sure try/except/finally is used to assure resource clean-up where it's relevant. I'm pretty sure I'm already doing that (out of habit), but another look-through wouldn't hurt

Checking Compliance with pylint

There's a tool called pylint that can be used to check and rate Python code. It checks a variety of items across an entire code-base, including many (if not all) of the various PEP-8 standards, and generates a report of all the various items that it finds, plus a rating of the code it analyzed on a 1-to-10 scale. Knowing that I have a slew of varied violations likely, I was expecting it to pretty much report that the code I was writing was somewhere in what might be called a horrible state, and that expectation was borne out when I ran it against the idic package just before the markup module was finished:

% errors / warnings by module
-----------------------------

+-------------------+------+--------+---------+-----------+
|module             |error |warning |refactor |convention |
+===================+======+========+=========+===========+
|idic.markup        |56.00 |39.89   |39.53    |49.95      |
+-------------------+------+--------+---------+-----------+
|idic.doc_metadata  |44.00 |42.13   |44.19    |30.71      |
+-------------------+------+--------+---------+-----------+
|idic.unit_testing  |0.00  |12.92   |11.63    |14.70      |
+-------------------+------+--------+---------+-----------+
|idic.serialization |0.00  |5.06    |4.65     |4.63       |
+-------------------+------+--------+---------+-----------+


Messages
--------

+-------------------------------+------------+
|message id                     |occurrences |
+===============================+============+
|bad-whitespace                 |5259        |
+-------------------------------+------------+
|bad-continuation               |1319        |
+-------------------------------+------------+
|trailing-whitespace            |1212        |
+-------------------------------+------------+
|invalid-name                   |538         |
+-------------------------------+------------+
|protected-access               |91          |
+-------------------------------+------------+
|unidiomatic-typecheck          |88          |
+-------------------------------+------------+
|line-too-long                  |27          |
+-------------------------------+------------+
|(There are more)                            |
+--------------------------------------------+
The bad-whitespace, trailing-whitespace and invalid-name items were more or less expected to be widespread, for the reasons I mentioned above. I'll have to look at the details on the bad-continuation items, to see if they are really a problem, or if they're something in my coding-style that I'm doing for an identifiable reason. The protected-access items are, I'm sure, places where I'm directly accessing a protected member of one class-instance from within an instance of another class, probably in cases where I need to set a property-value in the first from within the second, and that property is read-only from a public-interface perspective, but I'll take a deeper look at some point later to determine if that's really a problem. The unidiomatic-typecheck items will warrant a closer look as well, since I'm not sure what those entail. At and below the count/level of the line-too-long items, I'm not terribly concerned — so long as the code is functional and testable, it seems likely that they are minor, tweaky little things. That said, there are 45 reported item-types, and 224 individual items, so those will probably warrant a closer look before I wrap up the modules in the current idic package-structure. The final rating, using pylint defaults is reported as:
Global evaluation
-----------------
Your code has been rated at -18.45/10

On a scale from 0 to 10, -18.45 sounds pretty bad, but I suspect that a lot of that bad raing will go away once the bad-whitespace, trailing-whitespace and invalid-name items are ignored. Doing that is pretty straightforward, and can even be set up so that the rule-set for the idic package is all stored in one place, so that a pylint run that has specific exceptions relating to the idic code-base won't contaminate pylint runs against other packages and modules:

  • Create a custom pylint configuration-file:
    $ pylint --generate-rcfile > pylint.idic.rc
  • Add the bad-whitespace, trailing-whitespace, invalid-name, and protected-access messages to the disable list (I prepended hem in my case, since there were already quite a few in the list generated in the pylint.idic.rc file generated by pylint itself):
    [MESSAGES CONTROL]
    # ...
    
    # Disable the message, report, category or checker with the given id(s). You
    # can either give multiple identifiers separated by comma (,) or put this
    # option multiple times (only on the command line, not in the configuration
    # file where it should appear only once).
    
    # ...
    
    disable=bad-whitespace,trailing-whitespace,invalid-name,protected-access # ...
    
  • Re-run pylint using the package-specific configuration-file:
    pylint --rcfile=pylint.idic.rc idic
That yields:
% errors / warnings by module
-----------------------------

+-------------------+------+--------+---------+-----------+
|module             |error |warning |refactor |convention |
+===================+======+========+=========+===========+
|idic.markup        |56.00 |55.17   |39.53    |63.63      |
+-------------------+------+--------+---------+-----------+
|idic.doc_metadata  |44.00 |26.44   |44.19    |16.95      |
+-------------------+------+--------+---------+-----------+
|idic.unit_testing  |0.00  |10.34   |11.63    |14.67      |
+-------------------+------+--------+---------+-----------+
|idic.serialization |0.00  |8.05    |4.65     |4.76       |
+-------------------+------+--------+---------+-----------+


Messages
--------

+-------------------------------+------------+
|message id                     |occurrences |
+===============================+============+
|bad-continuation               |1319        |
+-------------------------------+------------+
|unidiomatic-typecheck          |88          |
+-------------------------------+------------+
|line-too-long                  |27          |
+-------------------------------+------------+
|(There are more)                            |
+--------------------------------------------+
... and a rating of 4.38:
Global evaluation
-----------------
Your code has been rated at 4.38/10 (previous run: -18.45/10, +22.83)
If all of the various bad-continuation items are either corrected, or deemed to be something I'm doing for a reason, and go away (simulated by adding that message to the configuration-file's disable list), the results are still better:
% errors / warnings by module
-----------------------------

+-------------------+------+--------+---------+-----------+
|module             |error |warning |refactor |convention |
+===================+======+========+=========+===========+
|idic.markup        |56.00 |55.17   |39.53    |48.28      |
+-------------------+------+--------+---------+-----------+
|idic.doc_metadata  |44.00 |26.44   |44.19    |31.61      |
+-------------------+------+--------+---------+-----------+
|idic.unit_testing  |0.00  |10.34   |11.63    |17.82      |
+-------------------+------+--------+---------+-----------+
|idic.serialization |0.00  |8.05    |4.65     |2.30       |
+-------------------+------+--------+---------+-----------+


Messages
--------

+-------------------------------+------------+
|message id                     |occurrences |
+===============================+============+
|unidiomatic-typecheck          |88          |
+-------------------------------+------------+
|line-too-long                  |27          |
+-------------------------------+------------+
|(There are more)                            |
+-------------------------------+------------+
and a rating of
Global evaluation
-----------------
Your code has been rated at 8.62/10 (previous run: 4.38/10, +4.24)
I'm going to leave the bad-continuation items in place until I have a chance to look at them in more detail, though.

From my perspective, knowing that my coding-practices are going to generate some pylint errors and warnings, I'm OK with disabling some of them (at least so far). If I were using a different IDE, one that handled certain tasks differently (I won't go so far as to say better), or if the code-base that I'm writing weren't intended to break some of the rules, it might well be a very different story.

I'd also give some serious consideration to including a pylint run as part of a quality-check during a build or commit process, assuming that I could figure out a good/useful way to integrate them. For now, I'll just run pylint periodically to see what the results look like, and as a check for any egregious omissions or errors on my part...

Tuesday, January 10, 2017

How I Write Code

I've been a developer (mostly web-applications) for longer now than I'd care to admit. In the nearly two decades I've been doing this kind of thing, I've acquired several habits that could be considered coding standards that I adhere to, even if they may not be typical best practices in the industry at large. Some of them are, I think, though I've had experience professionally where they were acknowledged but not followed, or set aside because of time/budget priorites. All of the following, though, I consider to be important enough that I plan to follow them while I work through the code I'm presenting here.

Yes, I Know Python's not Java...

Let me get this out of the way first...

Over the last several years, as I've contemplated various programmatic constructs and concepts and how to implement them in Python, as I've Googled them I've run across any number of discussions that follow a pattern like:

How can I [do something] in Python?
You shouldn't, It's not Pythonic.
How can I [do something] in Python?
You shouldn't, it violates duck-typing.
How can I [do something] in Python?
Why would you want to? Python's not Java
I'll be honest: These tend to irritate me, sometimes a lot. While there is certainly some benefit to keeping Python code pythonic, trying to figure out what, exactly, that really means is not easy, and is often at least a little bit inconsistent from one person's viewpoint to another. I did, eventually, run across an article that makes a solid attempt to define what pythonic really means, and it's worth a read, in my opinion. I noted with some interest, though, that one of the first things stated was that
Pythonic means something like idiomatic Python, but now we'll need to describe what that actually means.
before going on to actually explain and discuss what some of the idioms of Python actually are, and how they've evolved over time (and, maybe, are continuing to evolve). There is, maybe, enough insight into the then-current idioms discussed there to get a good handle on recognizing other, perhaps newer idioms.

That same article also mentions The Zen of Python, which can be found by executing import this in a Python shell (numbered for later reference):

The Zen of Python, by Tim Peters
  1. Beautiful is better than ugly.
  2. Explicit is better than implicit.
  3. Simple is better than complex.
  4. Complex is better than complicated.
  5. Flat is better than nested.
  6. Sparse is better than dense.
  7. Readability counts.
  8. Special cases aren't special enough to break the rules.
  9. Although practicality beats purity.
  10. Errors should never pass silently.
  11. Unless explicitly silenced.
  12. In the face of ambiguity, refuse the temptation to guess.
  13. There should be one – and preferably only one – obvious way to do it.
  14. Although that way may not be obvious at first unless you're Dutch.
  15. Now is better than never.
  16. Although never is often better than right now.
  17. If the implementation is hard to explain, it's a bad idea.
  18. If the implementation is easy to explain, it may be a good idea.
  19. Namespaces are one honking great idea – let's do more of those!
These may be good general characteristics for identifying some of the Pythonic idioms. They are cited frequently enough (usually without explanation or context as applied to answering a How can I [do something] in Python question) that it appears many believe them to be adequate.

<soapbox>

But, particularly with respect to practicality beating purity, (#9), there are other considerations that I believe are at least as important. Perhaps even more important, if the alternative is code that is unusable, brittle or difficult to maintain.

</soapbox>

My Coding Imperatives

In general, I believe that all code should be written to meet the following criteria (setting aside any specific functionality or implementation guidelines). They aren't in any particular order, but I've numbered them for reference later:

  1. It should be as easy to use as possible;
  2. It should be as hard to misuse as possible;
  3. It should be as well- and consistently documented as possible;
  4. It should be as general-purpose as possible, balanced against the functionality it was intended to provide;
  5. It should thoroughly tested;
  6. It should be as unsurprising as possible;
These are very generic and very global, but each of them is a step towards improving code quality and usefulness. If code isn't useful, or doesn't have some minimum degree of quality, then I start to wonder why it even exists.

The third item (regarding documentation) is something that I'm going to address with a documentation module in one (or more) of the posts here. I'm also considering ways to integrate documentation-requirements into the thoroughly tested item (#5), but I don't know how far I'm going to get with that, or if it'll even really be necessary, since I plan on incorporating the documentation-process into code-templates as soon as I have it worked out.

Guiding Principles

The balance of those items, though, have led me to formulate a set of guiding principles that I follow with any code that I write that's going to be available to anyone other than myself. I frequently adhere to them even for code that I'm not planning on sharing or distributing, particularly if I expect that I'll need to set it aside for any length of time — after a while, I'll be as much a stranger to my own code as someone who's never seen it before. At that point, any of the old adages about commenting your code (because the sanity you save may be your own) apply as much to the application of these principles:

  1. Manage/control all public interface entities/members
  2. Raise errors as close to their ultimate source as possible
  3. If specific types are expected, test for those where they're expected
  4. If specific types are expected, assure that some common type-identity is defined or available to use to test the expectation
  5. Restrict inheritance until there is a demonstrable need for it
  6. Leverage inheritance to keep functionality in one and only one place

The specific shape of these — how they are implemented — may vary significantly depending on the language or platform(s) in use. They may even take some of their final shape from business- or project-level requirements. Here's how I see them applying to what I'm planning to do here, and specifically in Python...

Manage/control all public interface entities/members

The public interface of a class is important. Ultimately, it controls all avenues of interaction with each and every instance of the class. Python doesn't have real protected- or private-member capabilities — it's enforced by convention (and name mangling for private members), which does not prevent access to those members. Taken together, these facts open up a whole range of possibilities for bad interactions with instance members, though probably only with respect to instance attributes (properties). In application, what this usually means is that I'll define all public properties of a class using the formal property provided by Python, with getter, setter and deleter methods. The class-templates that I've shown already (the ones that allow for concrete properties, at least) have already hinted at this in their structure. An implemented (but very basic) properties-structure would look something like this for a property named PropertyName somewhere in a class:

_propertyName = None  # The internal storage of the property

def _GetPropertyName( self ):
    """
Gets the PropertyName property-value of the instance."""
    return self._propertyName

def _SetPropertyName( self, value ):
    """
Sets the PropertyName property-value of the instance."""
    # TODO: Type-check the value argument, raising an error if bad.
    # TODO: Value-check the value argument, raising an error if bad.
    self._propertyName = value

def _DelPropertyName( self ):
    """
"Deletes" the PropertyName property-value of the instance by setting it 
to None."""
    self._propertyName = None

PropertyName = property( _GetPropertyName, None, None,
    'Gets the PropertyName value of the instance.' )

In my expereience, most property-getter methods (_GetPropertyName) that don't refer to some other object, or that don't implement some sort of lazy instantiation or -loading strategy are very naïve. They almost have to be, though, since all they usually need to do is retrieve the interal-storage value that the property wraps, and outside of a few special cases, I rarely find myself doing more than this example shows. Most of my deleter-methods, barring variations in the deleted value being set are also pretty naïve...

The setter-method shown (_SetPropertyName) is also naïve as shown, but that naiveté would go away as soon as the type- and value-checks noted in the TODO comments were implemented. The implementations of those checks ties directly into my next guiding principle, so I'll cover that in more detail shortly. At least one of those checks (checking for types, usually) shows up in almost every setter-method I write, eventually.

Raise errors as close to their ultimate source as possible;
If specific types are expected, test for those where they're expected; and
If specific types are expected, assure that some common type-identity is defined or available to use to test the expectation;

These tend to be expressed in my code as type- and value-checking of function and method arguments more than anything else. That, in turn, tends to look a lot like emulating static typing of values and arguments, as is found in Java and all of the real C variants. To be brutally honest, there's a fair amount of truth to that observation (accusation?) — but I strongly believe that there's a significant contribution to several of my goals (#2, #5 and maybe #6, though that's often situational). A simple implementation, using the same setter-method shown above, and checking for a non-empty string or unicode value or None would look something like this:

def _SetPropertyName( self, value ):
    """
Sets the PropertyName property-value of the instance."""
    if value != None and type( value ) not in ( str, unicode ):
        raise TypeError( '%s.PropertyName expects a non-empty str or '
            'unicode value, or None, but was passed "%s" (%s).' % ( 
                self.__class__.__name__, value, 
                type( value ).__name__
            )
        )
    if value != None and not value:
        raise ValueError( '%s.PropertyName expects a non-empty str '
            'or unicode value, or None, but was passed "%s" (%s).' % ( 
                self.__class__.__name__, value, 
                type( value ).__name__
            )
        )
    self._propertyName = value

It's not difficult to imagine a scenario where, say, an instance property-value gets set to some value, other code happens, and somewhere along the line, perhaps a lot further in, an error gets raised because that initial value-setting was invalid. This kind of thing happens all the time, especially during the development phase of a project. Although Python's error-reporting (and traceback) functionality is pretty solid, this sort of bug can be difficult and painful to deal with. If, on the other hand, the initial value-set process actively checks for valid values (by type and/or actual value) and raises an error right then and there, it doesn't have a chance to get buried in other code after silently setting up the imminent failure. While I like Python's dynamic typing, and I like not having to specify a type for each and every variable, property and argument, there are times when it's advantageous to do so, and this sort of scenario is, hands down, one of those times.

There are some trade-offs that have to be made, though, if the more open, dynamic nature of Python code is to be preserved while still actively type- and value-checking potential points of failure. First and foremost among them, I think, is that if type-checking is going to be used, there must be some very abstract types defined for any checks that aren't looking for baseline or primitive types. That is: If a method expects a number for a given argument, checking the type of that argument is easily managed by looking to see if the value is of a numeric type (int, long, float, etc.). If an argument is expecting some other type, say an XML node (whether it's a tag, a text-node, CDATA or a comment), there must be some low-level type or interface (maybe something called IsDOMNode for example) defined that can be used to actually check the incoming value-type. That's not difficult, but it requires planning and/or discipline to make sure it gets done.

Restrict inheritance until there is a demonstrable need for it

This is, I gather, a pretty controversial practice, and not just in the realm of Python code. The typical argument against it that I see in Python-oriented discussions is usually some variant of the argument that we're all consenting adults here, so let me subclass as I see fit. OK. Fair enough. As the other consenting adult in this relationship, my concern would be that someone might want to subclass something in my code, even legitimately, that flat-out was not intended to be subclassed. That someone might well be me some time later, after I've long since forgotten why a class wasn't deemed safe or desirable to subclass. Since I'm planning on distributing the actual code, there's nothing to prevent it happening, but if it's going to happen, I'm going to require that doing so requires at least looking at the original code. I'll commit to making sure I explain why it's not intended to be extended in the code itself. After that, and hopefully after actually looking at the explanation, any other consenting adult can make their own decision, hopefully an informed one, with the understanding that they do so at their own risk.

Leverage inheritance to keep functionality in one and only one place

Python is unusual (maybe unique) in that it allows nearly unrestricted multiple inheritance — there are a few restrictions, to be sure, but they are nowhere near as stringent as the ones found in other languages, where, typically, any given class can only inherit from one other class, concrete or abstract, and any number of interfaces. Inheritance can be something of a bugaboo, breaking encapsulation (there are several explanations and examples of how this can happen that can be found easily – take your pick). I have settled on what I believe to be a pretty solid balance between using inheritance and preserving encapsulation by using small abstract classes as mixins for very specific, usually simple, pieces of common functionality. Technically, this still presents the inheritance-breaks-encapsulation risk, but if the functionality is simple, cohesive, and sensible, the ability to keep all of its code in one place is, to my thinking, a good trade-off for that risk.

Less-Imperative Items and Next Steps

I generally like to have templates and/or snippets set up for development, so that I don't have to spend (read as waste) a lot of time creating starting-points for common items like modules and classes. I mentioned incorporating the documentation-process into code-templates earlier, but before I can lock down any of the starting-point templates noted above, I need to work out how I'm going to provide in-code documentation so that it can be included in those templates. There's a fair amount of thought that has to go into that, and I'm not sure if I'll be able to get any real code interspersed into it, but I'm going to try...