Showing posts with label architecture. Show all posts
Showing posts with label architecture. Show all posts

Sunday, December 21, 2014

The Rule Of Law

A layman's view of The Rule of Law. IANAL.

Contents

This I Believe

Some years ago NPR started running a series called This I Believe as a tribute to Edward R. Murrow and his original 1951 radio program of the same name. As I commuted I would occasionally catch an episode and hear an essay about the topic in which a contributor believed. I would listen to an essay around a weighty topic as God, Love, Funerals, Good and Evil or Public Service and think, "no", "maybe", or "yeah, sure". Then one day I heard Michael Mullane's essay on The Rule Of Law and I thought "Yes! This I believe!"

I particularly liked Michael's point that the Tinkerbell effect applies to the Rule of Law. As he says, God exists (or does not exist) whether or not you or I believe that to be so, but with the Rule of Law, it can only exist if almost all of us believe in it and follow it. As Death says to a human near the end of Terry Pratchett's Discworld book Hogfather, "YOU NEED TO BELIEVE IN THINGS THAT AREN'T TRUE. HOW ELSE CAN THEY BECOME?" (Death always talks IN CAPITAL LETTERS.)

The Importance of Law

Why is law important? The American Declaration of Independence asserts that "all men are created equal" and The Universal Declaration of Human Rights asserts that "all human beings are born free and equal in dignity and rights." To support that position we need a system of law that in fact treats all people equally. But even if the law does not protect all of the fundamental human rights, it can provide an important benefit to its society: stability through predictability.

To be predictable, the system of laws must be:
  • Understandable - the laws can be understood by most people.
  • Consistent - individual laws do not conflict with each other.
  • Extensive - the laws cover all common situations and a large portion of less common situations.
There have been many successful nations that followed the Rule of Law with different laws for different classes of people, including Rome and Greece. A system of law can provide stability and a foundation for an orderly and effective society without treating all people equally.

Prescriptive and Proscriptive Law

Prescriptive laws are those that tell us what we must do, such as Honour thy father and thy mother. Proscriptive laws are those that tell us what we must not do, such as "Thou shalt not kill".

You can think of prescriptive law as additive manufacturing: you can start with nothing, and add pieces until you get something useful, like building up a sculpture by adding little pieces of clay, or 3D printing.

Proscriptive law is more like subtractive manufacturing: you start with a block of something and carve away pieces until you get the desired result, like starting with a chunk of marble and carving a sculpture out of it, or machining.

(But don't try searching for additive law or subtractive law unless you are working with primary colors. :-) )

Given the assumption of freedom in both of the Declarations above, it's easier to start by saying people can do anything, then add proscriptive laws specifying what they can't do. Compared to the complete freedom and anarchy of a society with no laws, you can get pretty far down the road to stability just with proscriptive laws. Of the Ten Commandments, eight are proscriptive and only two are prescriptive.

Law versus Convention

While the Rule of Law normally refers to the explicit and codified laws on the books, which can be enforced by the state, there is another set of rules that most of us live by which are not legally mandated. These conventions include social guidelines that prescribe how to behave and communicate, including when and how it is appropriate to touch (such as shaking hands or a pat on the back), to ask for something (with "please" and "thank you"), to offer advice ("true/kind/necessary") or apologies, and many other behaviors.

These conventions don't have the force of law. If you break these rules, you won't be sent to jail or be forced to pay someone monetary damages - but you might find that you are a little less successful and your life might be a little less pleasant. Like laws, conventions are only useful if most of us agree on them, and like laws, a widely accepted and understood set of conventions helps make the world a little bit more predictable, which in turn makes it a little bit easier for people to make plans and be successful.

In effect, social conventions are simply another layer of "laws" that sit below the constitutional laws and the statute laws (and in reality the American legal system has many other levels than just those two).

Multiple Systems of Law

I am intrigued by the fact that we have so many different implementations of the Rule of Law. Every nation on Earth that abides by the Rule of Law has its own system of law. The ways in which the laws of nations interact is as varied as the relationships between the nations. For example, American Law has specific sections dealing with the fact that there are Native American "domestic dependent nations" within its borders that have their own laws.

Similarly, every nation has a different set of social conventions, those unwritten rules that lubricate our everyday interactions.

On top of all those different systems of law, we have International Law, with the intent of providing structure for interactions between nations when those nations have different and possibly incompatible systems of laws. Two aspects of International Law that I find particularly thought-provoking are the Law of War, and Jurisdiction.

(For an interesting bit of history about Jurisdiction, read about Peine forte et dure.)

Meta Law

In order to be predictable, the laws must be stable and not change often; but the laws must sometimes be changed in order to cover new situations or to correct problems in existing laws. One approach to improving the predictability of the system of laws while still allowing for change is to use a layered approach, where some laws are considered more important than others and are thus harder to change. The set of harder-to-change laws typically includes the rules on how to change the laws. This is the basis of the constitutional model, as is used in the United States, in which the most important laws are embodied in the constitution, with rules that make those laws much harder to change than regular laws. A constitution will typically include rules on how both "normal" rules and the rules embodied in the constitution can be changed.

Back in 1982, a "constitution" game by Peter Suber called Nomic appeared in Douglas R. Hofstadter's column, "Metamagical Themas," in Scientific American. In this game, players take turns proposing changes to the rules of the game. The rules start out in two categories, "immutable" and "mutable", corresponding to the simple two-level "constitutional" and "statute" law that Americans are taught in civics classes. The rules of the game tell how a player wins the game, and also tell how the rules can be changed - including how to change the rules that tell how to win and how to change the rules. The Nomic game is intended to illustrate the mechanisms and possibilities described in Peter Suber's book The Paradox of Self-Amendment, available online. For the quickest read on the game, you can jump straight to the rules, but the game description, although somewhat lengthy, is also interesting.

In Suber's book he starts by asking how a legal system can deal with paradox, when there are laws that directly contradict each other, and he notes that "paradoxes come and go without much notice and are dealt with without much ado."

Given that systems of laws seem always to be self-referential (since they include rules about how to change the rules), attempting to craft a system of laws that is also complete and consistent would seem to run into a version of Gödel's Incompleteness Theorem. In practice, systems of laws are not really complete and still blithely violate consistency, yet manage to be quite useful despite their flaws.

Law and Software

The title of this section might refer to laws that affect software, such as copyright law, or it might refer to the use of software to assist in the application of law, such as computerized law indexes or Regulation by Software; but in fact, I am referring to the use of law as a concept in defining how software works.

As in a society, a programming language is built on a set of rules that describe how statements in the language are interpreted by the computer. The developer uses his knowledge of these rules to create a program that instructs the computer to do something that is useful to the developer.

Imagine trying to program in a computer language with no rules. How could you get anything done? You could never predict the results of a statement, so you could never make a program that produced anything predictable.

Just as different societies each have their own set of rules, different programming languages each have their own set of rules. And just as with social conventions, different groups of programmers typically adopt programming conventions that are not enforced by the compiler but are intended to make life a bit simpler for the developers in the group.

In fact, all of the concepts discussed above are applicable to software. You can consider that as we take a look at what it means to define software in terms of laws.

Law and Object-Oriented Programming

Back in 1987 Naftaly Minsky and David Rozenshtein published "A Law-Based Approach to Object-Oriented Programming" (available for purchase on-line) in which they discussed how an object-oriented system can be described in terms of the laws that control the exchange of messages between objects. (Minsky has published quite a few other papers on related topics concerning law and software.)

They start by defining objects as containing state and program, with four primitive messages (prefixed by the octothorpe character, #) to create (#new) and destroy (#kill) objects and to get (#get) and set (#mutate) state. Messages are defined as a triplet of sender, message text, and target. Message delivery goes through the law system, which can take one of three actions:
  1. The message can be delivered to its target.
  2. The message text and/or target can be modified and then delivered.
  3. The message can be blocked and thus not delivered.
With these definitions and a permissive law that allows any object to send any message to any other object, the system does not exhibit many of the characteristics typically associated with object-oriented systems.

They then examine the effect of different kinds of laws, such as allowing primitive messages to be sent only by the same object. Through this approach they show how to implement common object-oriented features such as encapsulation, inheritance, and class variables as well as less common features such as multiple inheritance, exclusion of methods from inheritance, and triggers.

Given that the program is part of the state of the object, it can be modified with a #mutate message, so it is possible to describe self-modifying programs within this framework. The laws of the system control whether and how this message is allowed to be sent.

By defining the laws in objects that are themselves part of the system, those laws can then be changed. The system could start with a separate subset of laws that control how the laws can be changed, making this approach look very much like Suber's Constitution game.

The law system allows the laws to modify the content of a message or redirect it to a different target, allowing for the implementation of security checks and other forms of enforced delegation.

I am not aware of a production system that directly uses this style of law-based control of message passing, but there are some systems that use a conceptually similar method of applying a set of rules to some messages to control their delivery. For example, in the Java security model different environments can have different implementations of the SecurityManager, each with its own definition of the security policy (i.e. rules) that controls whether certain actions are allowed to be taken, which can be viewed as allowing the messages requesting those actions to be delivered. The OSGi security model goes further towards being a general law-based system, including the ability to specify rules via a string and to compose multiple security policies.

Law and Mind

For both societies and software, laws are rules telling us what we must and must not do, or do differently, and conventions are rules telling us what we should and should not do, or do differently. By following these rules and conventions, a society or a software system can be far more productive than one with the same underlying capabilities but where the rules and laws are less cohesive and effective or are not followed.

Could it be that the same is true for our minds? According to Marvin Minsky's theory of the mind as set forth in Society of Mind, our minds are composed of many small agents communicating with each other. Minsky's agents are very small pieces, and the communication between them is below our level of awareness. Perhaps our minds use something like Naftaly Minsky's law-based message delivery mechanism to monitor and control these low-level communications between agents.

Maybe the biggest difference between people who are productive and those who are not is in the different internal rules the two minds follow, and not so much a difference in raw underlying capability. Maybe productive people have a better set of mental rules controlling the messages within their minds. And if that is true, that leads to an interesting question: to what extent is it possible for people to rewrite their own low-level internal communication rules to improve their performance, and how might that be accomplished?

Sunday, November 2, 2008

Seamful Design

Whenever I hear someone use the phrase "seamless design" in reference to a large software system, I cringe inside. Perhaps the examples I have seen are not representative, but in my experience when people try to design a large seamless system they usually end up with a large system with poorly defined and mostly hidden seams, and it's an ugly mess.

Personally, I have no problem with seams per se. I don't think I have any seamless shirts, yet that has never bothered me. Some of my shirts even accent the seams with a different color thread so that they stand out. When done well, the design of the seams adds to the aesthetics of the shirt.

There are very few large man-made objects without seams. Continuously poured concrete freeway lanes are very large, yet even there they put in seams where appropriate, such as for expansion joints, and there are well defined seams between the lanes. A seamless road is very nice until it needs a bit of repair. Then it gets patched, and you have something much uglier than a seam. Or the ground shifts a bit and causes irregular cracks, which are like ugly, poorly designed seams.

Concrete sidewalks could be made seamless for long lengths, like freeways, but usually, in addition to real seams for expansion, they put in fake seams by scribing additional lines across the sidewalk. These control joints are designed so that, if the sidewalk shifts and cracks, it is more likely to do so at the control joint, where it will not mar the appearance of the sidewalk. They also make repairs much nicer: you can rip out and replace one sidewalk square rather than putting in an irregular patch or replacing the entire section between real seams. Those sidewalk designers were not stupid.

In my view, software systems are like shirts and sidewalks: they should have nice seams in the right places. When done well, those software seams can add to the aesthetics of the system and make it simpler to maintain. When I say "software seams" I am of course referring to the interfaces between the different components of the system. A system with well defined interfaces can be a thing of beauty. Without those well defined interfaces, the system may "crack" in random places like a too-large slab of concrete.

The larger the system, the more important it is to have well defined interface points. If your business is successful and continues to grow, eventually you will exceed the capacity of your system. If you have a truly seamless system, you may have to replace the whole thing. With well defined interfaces, you have the option of upgrading only the parts that are at capacity. Similarly, if you ever want to migrate to newer technology for any reason, or integrate with another system, having those interface points makes it possible to migrate or integrate the system in parts, which is generally less expensive, less risky, less impacting, and more flexible.

The next time someone starts talking to you about seamless software, think about seamless shirts and sidewalks before assuming that "seamless" is a good thing.

Wednesday, October 22, 2008

Software Quality Dimensions

In my role as a Software Systems Architect, in positions as both employee and consultant, I have critiqued and contributed to existing and planned systems. When doing so I consider a number of different issues, most of which I was not taught in school. Although the title of this post refers to software, I also like to consider the environment in which the software is developed and used, so some of the issues discussed below are purely software while some are related to hardware or other aspects of the development or operating environments. There are relationships between some of these issues, but for the most part they are orthogonal, so each must be separately considered to ensure sufficient quality along that dimension. Strip out the comments after the colons and you could use this as a checklist to see how your system measures up.

Contents

Basics

Most developers know about these issues. They apply to almost every software system, no matter how small.
  • Functional: This is the area most people initially think of when considering software quality, and is the area most visible to end users.
    • Correct: the system must perform the right operations, preferably in a way that matches the user's expectations. The "principle of least surprise" is applicable here.
    • Usable: the system should be as easy to use as is practical. In colloquial terms, it should be user friendly.
    • Consistent: the different parts of the system should appear similar to the user. There should be model of the system presented to the user that is easily understandable and as simple as possible. Consistency among different parts of the system allows the user to understand those different parts with less overall to learn.
  • Performant: Performance improvements are usually thought of as quantitative changes, but a big enough change in performance becomes a qualitative difference in the system. As one of my friends says, performance is a feature.
    • Fast: the system must be fast enough to satisfy any real-time or near real-time constraints. Worst case scenarios must be considered: can the system keep up under the biggest possible load, and if not, what happens?
    • Small: the system must be able to operate within its resource constraints. If you can make the system smaller, that usually means cheaper, faster, and easier to maintain.
  • Maintainable: Systems can last for a long time, sometimes far longer than the original designers might have thought (as happened with the Y2K problem). For such systems, the cost of maintenance over the life of the system can be far more than the original cost of development.
    • Modular: the system should be subdivided into separable parts with well-defined integration points between the parts. This allows separate replacement or upgrading of just some parts rather than the whole system.
    • Documented: significant aspects of the system design should be captured in written form to reduce the loss of critical knowledge when employees leave the project.
    • Commented: a specific form of documentation. Code is easier to write than to read, but is typically read far more often than it is written. Appropriate comments simplify the task of reading and understanding the code.
    • Standardized: minimal and standard languages/tools/subsystems should be required. Fewer languages, tools and subsystems such as databases, operating systems and hardware platforms simplifies finding the resources needed to maintain the system.
    • Versioned (change-controlled): all source code and other system configuration information should be stored in a version-controlled repository so that new versions can easily be created when fixes are required, and old versions can be recovered when necessary to audit problems or restore functionality that was unintentionally lost in an upgrade.
    • Testable: the system should include automated unit and system tests to provide confidence that changes to the system have not broken anything. The system should be designed to support effective tests.

Hardening

For larger systems or systems with more valuable data, these capabilities have particular importance. They are more expensive to implement than the Basics, so are often omitted from smaller systems. They can also be very difficult to retrofit to a system that was not originally designed with them in mind.
  • Secure: Systems must be protected against unauthorized use.
    • Access control: the first line of defense.
      • Physical: anyone who has physical access to the system can cause problems. Console access is often not secured as well as remote access, and even if the person can't get in to the software, he can still disconnect or physically damage the system. Depending on the size of the system and the value of its data and services, this could mean physical locks on machines, locked rooms, or secure data centers with controlled access, including ID card with photo, signature, and biometrics such as handprint or retina scan.
      • Network: the system should include firewalls to limit access to only the services the system is designed to provide. For more secure systems, IP address filtering, VPN connections, or TLS certificate requirements can limit who can get to the system.
    • Authentication: users must be identified. This is typically done by requiring a username and password. More secure systems can require biometrics, such as a thumb scan, or a physical device such as an RFID security card. Every user should have a separate account to allow tracking exactly who it is that is logging in to the system.
    • Authorization: once a user is identified through the authentication process, the system can load up the privileges assigned to that user. Each user should have only the privileges necessary to do his job. Giving everyone complete access to the system increases the probability that someone will do something he should not do, whether intentionally or by accident.
    • Auditing: the system should record all pertinent information to allow a definitive determination of history for all significant security events. Details should include at a minimum what was done, who did it, and when it happened.
    • Internal checks (against bugs and attacks): for improved security, systems should run self-checks to ensure that critical parts of the system continue to run correctly. Ideally the self-check applications should be able to monitor themselves (or each other if more than one) to guard against problems within the self-check programs (including malicious users attempting to circumvent the self-checks).
    • Resource management/limits: the system should include mechanisms to allow limiting the resources consumed by each user, including disk space and CPU usage. In addition to allowing for a more fair use of the system by preventing one user from hogging resources, these mechanisms help prevent DOS (denial of service) attacks.
  • Robust: Nothing is perfect. A well designed system takes into account many different kinds of possible failure. The probability of failure can not be completely eliminated, but can be made very small given sufficient resources. The cost of failure must be compared to the cost of building a system with a sufficiently low probability of failure.
    • Redundant: the system should have no (or minimal) single points of failure. Redundancy can be applied at many levels: a single box can have multiple power supplies, multiple CPUs, multiple disks, and multiple network connections. Within one data center there can be multiple boxes and multiple network routers, with battery backup power. For maximum redundancy there can be multiple geographically separated data centers with multiple network routes connecting them.
    • Diverse: monocultures are more susceptible to damage. Just as with biological systems, diversity provides protection against problems that affect a "species". Sharing responsibilities among different operating systems and different applications provides defense against viruses that attack specific operating systems and applications, and against bugs in those components. This aspect of robustness can be very expensive, so is not often considered.
    • Forgiving (fault-tolerant): the larger the system, the higher the probability that it will have some problems, including bugs in the software. The system should tolerate small problems in the parts; a small problem should remain a small problem, not be amplified by cascading failures into a larger problem.
    • Self-correcting: Self-monitoring can be done at multiple levels, from memory parity up to application-level system checks. More sophisticated techniques allow for automatic correction of errors, such as Reed-Solomon coding instead of simple memory parity. Care must be taken to ensure that the probability of failure or errors in the error-correcting step is less than the parts that it is monitoring.
  • Scalable: If you expect usage of the system to grow over time, your design should allow incremental expansion of the system and should continue to perform well at the new usage levels. Scalability should be considered at multiple levels, depending on how far you need to scale. When scaling up by adding more identical units, you also get the benefits of redundancy, because with more units, the portion that you must set aside purely for redundancy can be reduced. Stated another way, effective redundancy can be added to a system much less expensively when that system is already using a collection of identical units for scaling purposes.
    • Scalable algorithms: algorithms should have appropriate big-O performance. Attention should be paid to word size limits to prevent overflow on large data sets.
    • Resource-tight: even a small memory leak can bring down a application when there is enough usage. The system should be tested under load and monitored to ensure it does not run out of memory, file descriptors, database connections, or other resources that could leak from poor coding.
    • Parallelizable to multiple threads or processes: the first step in spreading out to multiple units is to use multiple threads or processes on one machine. Areas of concern are shared memory and concurrency: deadlock or livelock, excessive blocking due to excessive synchronization, and stale or corrupt data due to insufficient synchronization. On a multiprocessor machine, be sure you understand the memory model, and be aware of possible cache coherence problems that can arise if code is not properly synchronized.
    • Parallelizable to multiple machines: when one machine is not enough, scaling up to multiple machines clustered in one location is the next step. For this level of parallelization, typical issues of concern are network bandwidth limits, how to distribute data for improved throughput, and what to do when one host in the cluster is down.
    • Geo-redundant: for the largest applications, having multiple data centers in geographically separated locations provides redundancy and disaster security, as well as sometimes providing improved performance as compared to a single location because of reduced network delays. Typical issues are dealing with network latency, managing routing between data centers, and data replication between data centers for improved performance and redundancy.

Business

In theory you could build a great system without considering these issues, but in practice you had better pay attention to them, else your business will not last very long.
  • Affordable: The art of engineering includes balancing costs against other qualities. The following costs should be considered:
    • Design and development: the labor costs of building the system.
    • Hardware and external software: the purchase costs of the system.
    • Maintenance: the ongoing costs of repairing and upgrading the system.
    • Operational: the data center costs of operation.
  • Timely: When a product hits the market can determine whether or not it succeeds. When a product is scheduled to hit the market should be factored into the design, both in terms of how much time is available for development, and in terms of what the expected environment will be like then.
    • Soon enough: taking too long to get to market is a well-known concern.
    • Late enough: the first to market is not always the most successful. Sometimes it is better to let someone else bear the costs of opening up the market.
    • Available support and parts: a good design will plan on using parts that will be cost effective when the product is shipping, which is not necessarily the same as what is cost effective when the product is being designed. This requires predicting the future, so can be tricky to get right.
  • Operational: If the system is successful, it may be used for a long time or by many people, amplifying the value of making the system easy to operate.
    • Visibility: the operator should be able to easily verify that the system is functioning properly. This includes being able to determine what level of resources are being used and that it is not being attacked. All of the following pieces of information should be readily available to the operator:
      • What version of what component is running where.
      • What is happening now.
      • What has happened recently.
      • Who is accessing now or recently.
      • Performance and other resource statistics.
      • Usage statistics (e.g. what function is most popular).
      • Error and warning conditions.
      • Debugging information (e.g. logging).
      • Post-mortem info (for bug fixes).
    • Control: the operator should be able to adjust aspects of the system as appropriate to maintain its proper operation and to protect its security. This includes being able to do the following:
      • Start and stop processes, applications or hosts.
      • Manage access control, including adding, removing or modifying accounts, privileges and resource limits for users and groups.
      • Enable and disable features.
      • Perform maintenance.
    • High availability: for systems that require high availability once put into production, in addition to the robustness items listed above the following operations should be possible:
      • Piecemeal upgrades or replacement of components while running.
      • Ability to run with mixed versions of components (soft rollout), particularly on a system with multiple copies of the same component.
    • Granular control: for optimal resource management and flexibility in managing the business model of usage and payment, the system should support these capabilities:
      • Per-user restrictions, e.g. by time of day, stop when out of money.
      • Ability to charge customers per transaction or per other metrics (cpu usage, disk usage, #calls, etc).
    • Image: you might not be able to judge a book by its cover, and beauty is only skin deep, but people respond to how things look, so it is important to remember to maintain consistency and quality in these areas:
      • Branding: consistent look and feel concerning logos, company colors, slogans, etc.
      • Beauty: too often not included in software products.
    • Backups: any system with data of any value should be backed up. This is valid even for small systems, such as your laptop or home network.
      • You should have a backup schedule based on how much you would be bothered by losing your data. For a simple system, a regular schedule of full backups plus an automated incremental backup should suffice and is generally relatively easy to set up. For a larger system, you might want a full backup less often (due to the amount of data) with a layered schedule of weekly and daily backups.
      • Backup media should be validated. At a minimum, the backup media should be checked to make sure it contains the data that was intended to be written to it. For a more complete test, the backup data should be used to recreate a system in the same manner as would need to be done following a disaster.
      • Copies of backups should be stored off-site for disaster security. If your backups are stored on-site and the building is destroyed, those backups won't do you much good.
      • Sensitive data on backup media should be encrypted to prevent its use in the event that media is stolen.
I am always interested in continuing to learn, so if you think I have left out anything from my list, please let me know.