I wouldn't use the term ambiguous, but without a doubt there is a significant element of situational judgement.
To answer your question up front, no, there is no formal, mathematically verifiable means of testing for compliance to it.
The reason why is because not all software programs and domains are the same. Joel touched on this in his essay Five Worlds. Huge, enterprisey business process management software requires different design approaches than software controlling a vending machine.
I will recognize the SRP is one of the most judgement-heavy concepts of the SOLID group, and there are tons of bad descriptions out there. Often times, written by authors solidly in the e.g. enterprise consultingware world, writing absolute statements completely unaware or ignoring aspects of other worlds. The "one reason to change" line people often write I find especially nonsensical and vague. I think the SOLID principles are best suited to the enterprise consultingware types of software, and the professional authors e.g. Uncle Bob often make a living selling their services to those kinds of companies. So, unless you too are in that world, you need to recognize what's appropriate for your situation.
A single responsibility may mean different things to different people in different situations. As an example, I recently needed a class to deal with a program's settings. That one class reads the config file, parses it, has the means to access the configuration values, and can write the configuration back to disk. All of that, in a single class, ConfigurationSettings
. In my particular circumstance, despite doing those many different disparate things, I would claim my class does stick to a single responsibility: dealing with the configuration.
Now, some folks would say my design is defective, it does too much. I should inject in a config file parser object, have some abstract IConfig factory that instantiates the ConfigSettings class instead of new()
, or a bunch of other things to further break up the responsibilities of that class.
Who's right? It depends on how the single responsibility fits in the greater picture. I've seen some authors tack on "at a given level of abstraction" onto the description of the single responsibility, which I think makes a lot of sense. And so in my situation, at my chosen level of abstraction, my ConfigurationSettings
class does only a single thing, persist my program settings. The details of that config file format, where it's saved, etc, are things that just don't matter whatsoever in the greater picture, and no one cares where they go.
What kind of situation would there be where the abstract factory lovers would be right about my class? Often, they would be people in that mega-enterprisey world. My software has exactly 1 customer, and will never have more than that single customer. I can have an upgrade process that consists solely of "delete the old version, install the new one". If I introduce a change to the configuration settings that isn't backwards compatible to the old formats, I can just have them delete the old config and start fresh.
Not everyone can be so cavalier about their configuration settings. Some people may be writing software for hundreds or thousands of customers. They may have to manage and preserve different formats between different versions of their software. They may have to support a single configuration being shared by multiple computers, all reading from the same place. I don't have to do any of that. But for these other people, the file format, location, etc, does matter. In those situations, injecting a parser into my configuration class might make sense once you figure out what format the settings are in. It might make sense to have a factory do that figuring out. It might make sense to have a notification system for when settings get changed, instead of a bunch of static (global) variables like I'm using.
And if I was writing software for thousands of dental clinics to schedule appointments and do billing, my configuration class would without a doubt not be appropriate. But that's not what I'm doing, so it is.
Short of skynet-level AI, no static analysis tool is going to be able to look at a class, and understand what its intended level of abstraction is, and so thus can not make any meaningful determination of SRP compliance or not.