What makes a good software design? Can software be good and have a poor design?
I will address the second question first. Can software be good and have a poor design? I think so. Of course this depends on my defintion of good. Good software meets the current goals of the user. For instance the software does what the user wants and it does it with no errors, mistakes, or unwanted side effects. The user usually does not care what language the software was developed with, or what process was used to guide development, or how much the programmers were paid to write the software, or even if the software has a good design.
Software design is a practice that addresses issues the programmers find important. Such issues are changeability, extensibility, understandability, usability, and verifiability (if these are even words).
In my opinion code must meet the usability quality in order for the design to be considered good. What does this mean?
Code is developed to be called or in other words to be used. If the design is such that the code is easily called then it has an improved design. What makes code easily called? If it is easy to setup the state necessary to initiate a meaningful call. What makes state setup easy? That depends on the system and problem domain. Often this is an attribute of coupling and cohesion. Therefore, easily callable code is often losely coupled and cohesive code.
A good software design will make it possible to verify the software. How do you verify software? By using the code. Do you see the relationship? Usable code has a relationship to code verification. The easier it is to setup a call into the system maps directly to the ease of testing, validating, and verifying the sytem.
The test that exercises code is just another user of the code. Therefore when you write code you have at least to customers or consumers of the code. The first customer is some part of the overall system you are developing. The second customer is the test code that exercises the code you are developing. There are always at leat these two customers. If the first customer doesn't exist then why bother writing the code? If the second customer doesn't exist then what will you use in place of testing code to exercise your code?
A good software design is expressed in code that is usable. The easier it is to use the better. This ease of use reflects the coupling and cohesion of the abstract model. Usable software can be used by multiple customers. One customer should be a verification customer which is a piece of code to use your code and check if your code did what is expected. I believe this leads to the important axiom that testable code is code with a good design. I think that testability is a subset of usability.