Quality Indexes

After the submission of a Podspec to Trunk, the documentation service CocoaDocs generates a collection of metrics for the Pod. You can look these metrics for any Pod on metrics.cocoapods.org/api/v1/pods/[Pod]. These metrics are used to generate a variety of Quality Modifiers which eventually turns into a single number called the Quality Index.

This document is a form of literate programming within the CocoaDocs-API. As such it contains the actual ruby code that is ran in order to generate the individual scores. Plus, Swift looks like Ruby anyway - so you can read it ;).

The aim of the Quality Index is to highlight postive metrics, and downplay the negative. It is very possible to have a Pod for which no modifier is actually applied. Meaning the Index stays at the default number of 50. This is a pretty reasonable score.

A good example of the mentality we have towards the modifiers is to think of a Pod with a majority of it's code in Swift. It gets a boost, while an Objective-C one doesn't get modified. It's not about reducing points for Objective-C, but highlighting that right now a Swift library represents forward thinking best practices.

Finally, before we get started. These metrics are not set in stone, they have been evolving since their unveiling and will continue to do so in the future. Feedback is appreciated, ideally in issues - so they can be discussed.

Popularity Metrics

It's a pretty safe bet that an extremely popular library is going to be a well looked after, and maintained library. We weighed different metrics according to how much more valuable the individual metric is rather than just using stars as the core metric.

 Modifier.new("Very Popular", "The popularity of a project is a useful way of discovering if it is useful, and well maintained.", 30, { |...|
   value = stats[:contributors].to_i * 90 +  stats[:subscribers].to_i * 20 +  stats[:forks].to_i * 10 + stats[:stargazers].to_i
   value > 9000
 }),

However, not every idea needs to be big enough to warrent such high metrics. A high amount of engagement is useful in it's own right.

 Modifier.new("Popular", "A popular library means there can be a community to help improve and maintain a project.", 10, { |...|
   value = stats[:contributors].to_i * 90 +  stats[:subscribers].to_i * 20 +  stats[:forks].to_i * 10 + stats[:stargazers].to_i
   value > 1500
 }),

At the moment this is entirely focused on libraries that are coming from GitHub. In the future, once Stats for downloads/installs are mature then we will move over to that in order to accomodate libraries not using GitHub.

Swift Package Manager

We want to encourage support of Apple's Swift Package Manager, it's better for the community to be unified. For more information see our FAQ. This currently checks for the existence of Package.swift, once SPM development has slowed down, we may transistion to testing that it supports the latest release.

 Modifier.new("Supports Swift Package Manager", "Supports Apple's official package manager for Swift.", 10, { |...|
   cd_stats[:spm_support]
 }),

Inline Documentation

A lot of the generated documentation comes from inside the library itself. These metrics are about the usage of Appledoc and Headerdoc within your public API. This means either from the parts of Swift that you have classed as public or from the public headers.

Xcode uses this documentation to give inline hints, and CocoaDocs will create online documentation based on this documentation. Making it much easier for anyone using your library to work with.

 Modifier.new("Great Documentation", "A full suite of documentation makes it easier to use a library.", 3, { |...|
   cd_stats[:doc_percent].to_i > 90
 }),

 Modifier.new("Documentation", "A well documented library makes it easier to understand what's going on.", 2, { |...|
   cd_stats[:doc_percent].to_i > 60
 }),

Providing no inline comments can make it tough for people to work with your code without having to juggle between multiple contexts. We use -1 to determine that no value was generated.

 Modifier.new("Badly Documented", "Small amounts of documentation generally means the project is immature.", -8, { |...|
   cd_stats[:doc_percent].to_i < 20 && cd_stats[:doc_percent].to_i != -1
 }),

README Scoring

The README score is based on an algorithm that looks at the variety of the bundled README. You can run the algorithm against any URL here on clayallsopp.github.io/readme-score. A README is the front-page of your library, it can provide an overview of API or show what the library can do.

Strange as it sounds, if you are providing a binary CocoaPod, it is worth embedding your README.md inside the zip. This means CocoaPods can use it to generate your Pod page. We look for a README{,.md,.markdown} for two directories from the root of your project.

Note: These modifiers are still in flux a bit, as we want to take a Podspec's documentation_url into account.

 Modifier.new("Great README", "A well written README gives a lot of context for the library, providing enough information to get started. ", 5, { |...|
   cd_stats[:readme_complexity].to_i > 75
 }),

 Modifier.new("Minimal README", "The README is an overview for a library's API. Providing a minimal README means that it can be hard to understand what the library does.", -5, { |...|
   cd_stats[:readme_complexity].to_i < 40
 }),

 Modifier.new("Empty README", "The README is the front page of a library. To have this applied you may have a very empty README.", -8, { |...|
   cd_stats[:readme_complexity].to_i < 25 && spec.documentation_url == nil
 }),

CHANGELOG

Having a CHANGELOG means that its easier for people for compare older verions, as a metric of quality this generally shows a more mature library with care taken by the maintainer to show changes. We look for a CHANGELOG{,.md,.markdown} for two directories from the root of your project.

 Modifier.new("Has a CHANGELOG", "CHANGELOGs make it easy to see the differences between versions of your library.", 5, { |...|
   cd_stats[:rendered_changelog_url] != nil
 }),

Language Choices

Swift is slowly happening. We wanted to positively discriminate people writing libraries in Swift.

 Modifier.new("Built in Swift", "Swift is where things are heading.", 5, { |...|
   cd_stats[:dominant_language] == "Swift"
 }),

Objective-C++ libraries can be difficult to integrate with Swift, and can require a different paradigm of programming from what the majority of projects are used to.

 Modifier.new("Built in Objective-C++", "Usage of Objective-C++ makes it difficult for others to contribute.", -5, { |...|
   cd_stats[:dominant_language] == "Objective-C++"
 }),

Licensing Issues

The GPL is a legitimate license to use for your code. However it is incompatible with putting an App on the App Store, which most people would end up doing. To protect against this case we detract points from GPL'd libraries.

 Modifier.new("Uses GPL", "There are legal issues around distributing GPL'd code in App Store environments.", -20, { |...|
   cd_stats[:license_short_name] =~ /GPL/i || false
 }),

There were also quite a few libraries using the WTFPL, which is a license that aims to not be a license. It was rejected by the OSI ( An open source licensing body. ) as being no different than not including a license. If you want to do that, use a public domain license.

 Modifier.new("Uses WTFPL", "WTFPL was denied as an OSI approved license. Thus it is not classed as code license.", -5, { |...|
   cd_stats[:license_short_name] == "WTFPL"
 }),

Code Calls

Testing a library is important. When you have a library that people are relying on, being able to validate that what you expected to work works increases the quality.

 Modifier.new("Has Tests", "Testing a library shows that the developers care about long term quality on a project as internalized logic is made explicit via testing.", 4, { |...|
     cd_stats[:total_test_expectations].to_i > 10
 }),

 Modifier.new("Test Expectations / Line of Code", "Having more code covered by tests is great.", 10, { |...|
   lines = cd_stats[:total_lines_of_code].to_f
   expectations = cd_stats[:total_test_expectations].to_f
   if lines != 0
     0.045 < (expectations / lines)
   else
     false
   end
 }),

A larger library will increase the size of other people's application. Making them slower to launch. CocoaDocs look at the folder size of the library after CocoaPods has removed anything that is not to be integrated in the app.

 Modifier.new("Install size", "Too big of a library (usually caused by media assets) can impact an app's startup time.", -10, { |...|
   cd_stats[:install_size].to_i > 10000
 }),

CocoaPods makes it easy to create a library with multiple files, we wanted to encourage adoption of smaller more composable libraries.

 Modifier.new("Lines of Code / File", "Smaller, more composeable classes tend to be easier to understand.", -8, { |...|
   files = cd_stats[:total_files].to_i
   if files != 0
     (cd_stats[:total_lines_of_code].to_f / cd_stats[:total_files].to_f) > 250
   else
     false
   end
 }),

Ownership

The CocoaPods Specs Repo isn't curated, and for the larger SDKs people will create un-official Pods. We needed a way to state that this Pod has come for the authors of the library, so, we have verified accounts. These are useful for the companies the size of; Google, Facebook, Amazon and Dropbox. We are applying this very sparingly, and have been reaching out to companies individually.

 Modifier.new("Verified Owner", "When a pod comes from a large company with an official account.", 20, { |...|
   owners.find { |owner| owner.owner.is_verified } != nil
 }),

Maintainance

We want to encourage people to ship semantic versions with their libraries. It can be hard to know what to expect from a library that is not yet at 1.0.0 given there is no social contract there. This is because before v1.0.0 a library author makes no promise on backwards compatability.

 Modifier.new("Post-1.0.0", "Has a Semantic Version that is above 1.0.0", 5, { |...|
   Pod::Version.new("1.0.0") < Pod::Version.new(spec.version)
 }),

When it's time to deprecate a library, we should reflect that in the search results.

 Modifier.new("Is Deprecated", "Latest Podspec is declared to be deprecated", -20, { |...|
   spec.deprecated || spec.deprecated_in_favor_of || false
 }),

Misc - GitHub specific

This is an experiment in figuring out if a project is abandoned. Issues could be used as a TODO list, but leaving 50+ un-opened feels a bit off. It's more likely that the project has been sunsetted.

 Modifier.new("Lots of open issues", "A project with a lot of open issues is generally abandoned. If it is a popular library, then it is usually offset by the popularity modifiers.", -8, { |...|
   stats[:open_issues].to_i > 50
 })