HTML conversions sometimes display errors due to content that did not convert correctly from the source. This paper uses the following packages that are not yet supported by the HTML conversion tool. Feedback on these issues are not necessary; they are known and are being worked on.

  • failed: dirtree

Authors: achieve the best HTML results from your LaTeX submissions by following these best practices.

License: arXiv.org perpetual non-exclusive license
arXiv:2401.14244v1 [cs.SE] 25 Jan 2024

Contract Usage and Evolution in Android Mobile Applications

David R. Ferreira Faculty of Engineering, University of PortoPortoPortugal [email protected] Alexandra Mendes HASLab / INESC TEC & Faculty of Engineering, University of PortoPortoPortugal [email protected]  and  João F. Ferreira INESC-ID & IST, University of LisbonLisbonPortugal [email protected]
(2024)
Abstract.

Formal contracts and assertions are effective methods to enhance software quality by enforcing preconditions, postconditions, and invariants. Previous research has demonstrated the value of contracts in traditional software development contexts. However, the adoption and impact of contracts in the context of mobile application development, particularly of Android applications, remain unexplored.

To address this, we present the first large-scale empirical study on the presence and use of contracts in Android applications, written in Java or Kotlin. We consider different types of contract elements divided into five categories: conditional runtime exceptions, APIs, annotations, assertions, and other. We analyzed 2,390 Android applications from the F-Droid repository and processed more than 51,749 KLOC to determine 1) how and to what extent contracts are used, 2) how contract usage evolves, and 3) whether contracts are used safely in the context of program evolution and inheritance. Our findings include: 1) although most applications do not specify contracts, annotation-based approaches are the most popular among practitioners; 2) applications that use contracts continue to use them in later versions, but the number of methods increases at a higher rate than the number of contracts; and 3) there are many potentially unsafe specification changes when applications evolve and in subty** relationships, which indicates a lack of specification stability. Our findings show that it would be desirable to have libraries that standardize contract specifications in Java and Kotlin, and tools that aid practitioners in writing stronger contracts and in detecting contract violations in the context of program evolution and inheritance.

design by contract, software reliability, android applications, contracts, assertions, verification, Kotlin, Java, preconditions, postconditions, invariants
copyright: acmcopyrightjournalyear: 2024doi: XXXXXXX.XXXXXXXconference: 21st International Conference on Mining Software Repositories; April 2024; Lisbon, Portugalccs: General and reference Empirical studiesccs: Software and its engineering Software evolutionccs: General and reference Reliability

1. Introduction

Building reliable mobile applications is a growing concern as many companies are adopting iOS and Android as target platforms for their apps in critical domains such as mobility, health, finance, and government. There are now more mobile phones than people in the world: in 2022, there were more than 8.58 billion mobile subscriptions in use worldwide,111https://www.weforum.org/agenda/2023/04/charted-there-are-more-phones-than-people-in-the-world/ (accessed 17 November 2023) with more than 2 million apps available on the App Store and Google Play (Tao and Edmunds, 2018). Additionally, data from 2022 shows that Android represents approximately 43% of the overall operative system market share, followed by Windows (29%), and then iOS (18%) (Stats, 2023). Therefore, faults in mobile apps, and particularly in Android apps, can impact a very large number of users. In addition, with an increasing number of apps in critical areas such as health and finance, faults can have a huge negative impact. It is thus important to use software reliability techniques when develo** mobile applications.

One of these techniques is Design by Contract (DbC) (Meyer, 1992), under which software systems are seen as components that interact amongst themselves based on precisely defined specifications of client-supplier obligations (contracts). Contracts are specifications of an agreement between the client and the supplier of a component, where the supplier expects that certain conditions are met by the client before using the component (preconditions), maintains certain properties from entry to the component to exit (invariants), and guarantees that certain properties are met upon exit (postconditions). These contracts are written as assertions directly into the code. Currently, there are assertion capabilities in most programming languages, but assertions are not universally used. Since the 1980s, many have advocated DbC (Meyer, 1988) as an efficient technique to aid the identification of failures (Aniche, 2022), improve code understanding (Fairbanks, 2019), and improve testing efforts (Tantivongsathaporn and Stearns, 2006) which directly or indirectly contribute to more reliable software. This has led to a number of empirical studies on the use of contracts in a variety of contexts (Chalin, 2006; Schiller et al., 2014; Estler et al., 2014; Casalnuovo et al., 2015; Kudrjavets et al., 2006; Kochhar and Lo, 2017; Counsell et al., 2017; Dietrich et al., 2017). However, there are no previous studies on the presence and usage of contracts in Android applications nor any study that includes the Kotlin language.

In this paper, we present the first large-scale empirical study of contract usage in Android mobile applications written in Java or Kotlin. Our goal is to understand what is the adoption rate of contracts among Android developers, their evolution, and which language features are used to denote contracts. Information on how practitioners use contracts can aid the creation and improvement of tools and libraries by researchers and tool builders (Schiller et al., 2014). Additionally, empirical evidence about the benefits of contracts can encourage their adoption by practitioners and the establishment of DbC as a software design standard (Tantivongsathaporn and Stearns, 2006).

We aim to determine: 1) how and to what extent contracts are used, 2) how contract usage evolves in an application, and 3) whether contracts are used safely in the context of program evolution and inheritance.

In summary, the contributions of this paper are:

  • The first large-scale empirical study about contract usage and evolution in Android applications, resulting in a list of findings and recommendations for practitioners, researchers, and tool builders. Also, no previous studies consider Kotlin programs.

  • A list of language features, tools, and libraries to represent contracts in Android applications.

  • A dataset of 1,767 Java and 623 Kotlin Android applications, together with a pipeline of scripts that can be used to build large-scale datasets of Java and Kotlin open-source projects, guided by inclusion criteria and size optimization.

  • An updated and extended version of Dietrich et al.’s tool (Dietrich et al., 2017), which can now analyze Kotlin code and can be used to investigate additional Android-specific contracts.

It should be noted that, even though we update and extend Dietrich et al. (2017)’s tool, our work is not a replication of their study. Our study differs from their work by focusing on Android applications and not on Java applications only. Due to the focus on Android, our study considers Kotlin in addition to Java, as since 2019, the Kotlin programming language is the preferred language for Android app developers222https://techcrunch.com/2019/05/07/kotlin-is-now-googles-preferred-language-for-android-app-development (accessed 17 November 2023). Moreover, Kotlin is now used by over 60% of Android professional developers333https://developer.android.com/kotlin (accessed 17 November 2023).

Data & Artifact Availability

To support our study, an artifact was developed to automatically collect contracts from Android applications and to produce the necessary empirical data. The artifact is written in Python and Java, and includes an extension of the tool proposed by Dietrich et al. (Dietrich et al., 2017). All the code and datasets are publicly available: https://figshare.com/s/d6eb7e5522bb535dc81a

2. Contracts in Android Applications

Table 1. Contract elements considered in this study
category examples
IllegalArgumentException}\\
    (74 constructs) & \mintinline
javaEmptyStackException
SecurityException} \\
   ~& \mintinline
javaUnsupportedOperationException
AccessControlException} \\
   ~& \mintinline
javaIndexOutOfBoundsException
NullPointerException}\\
   %See \autoref
tab:cre-exceptions
org.apache.commons.lang.Validate.*}  \\
   (31 constructs) & \mintinline
javaorg.apache.commons.lang3.Validate.*
com.google.common.base.Preconditions.*}  \\
                       & \mintinline
javaorg.springframework.util.Assert.*
assert} (Java) \\
   (6 constructs)      & \mintinline
javaassert (Kotlin)
check(), checkNotNull()} (Kotlin) \\
                       & \mintinline
javarequire(), requireNotNull() (Kotlin)
org.jetbrains.annotations.*}  \\
   (136 constructs) & \mintinline
javaorg.intellij.lang.annotations.*
edu.umd.cs.findbugs.annotations.*}  \\
                       & \mintinline
javaandroid.annotation.*
androidx.annotation.*}  \\
                       & \mintinline
javajavax.annotation.* (JSR305)
Other
Dataset metrics.

From the initial list of 4,070 projects in the F-Droid index retrieved on May 21, 2023, we got 3,215 hosted in GitHub, 3,141 non-duplicated URLs, and 2,390 projects after filtering by the inclusion criteria. Out of these, 1,767 are Java applications and 623 are Kotlin applications. Since we tried to retrieve two versions for each application, we analyzed 4,192 program-version pairs. This means that for 294 applications, it was only possible to retrieve a single version. While these applications are still evaluated in the context of the usage and LSP studies, they are not considered for the evolution study.

LABEL:tab:data-metrics presents additional metrics about the final dataset size used in the empirical evaluation. Additionally, the table shows that the dataset is imbalanced, with more Java applications. The dataset includes 204,468 Java and 123,222 Kotlin compilation units and, therefore, Java represents 62.4% of the overall number of compilation units. This imbalance requires caution when trying to read this work’s results from the perspective of comparing Java against Kotlin’s use of contracts. Furthermore, the dataset includes 551,456 classes, 2,682,160 methods, and 300,639 constructors. Note that we did not consider private methods, because those methods are not used directly by a client, and a contract is a bond between a supplier and a client. In total, we analyzed 2,532,399 public, protected, and internal methods, and constructors. It is also worth noting the difference in the number of compilation units and classes between the two languages. Although the dataset contains 1.67 times more Java compilation units than Kotlin ones, it only includes 1.19 times more Java classes. Since a compilation unit usually represents a file, this means that there are more classes per file in Kotlin (2.04) than in Java (1.47). This is expected due to the more restricted Java rules that, for example, only allow a single top-level public class per file.

From the standpoint of the dataset’s diversity, it includes apps from various domains, such as gaming, communication, multimedia, security, health, and productivity.

4.3. Data Collection and Analysis

Here, we describe the analysis tool used and the three studies that we conducted to answer our research questions: the usage study, the evolution study, and the Liskov Substitution Principle study.

4.3.1. Analysis Tool

Our analysis tool is an extension of the tool created by Dietrich et al. (Dietrich et al., 2017), which was used in their study on the usage of contracts in Java applications. We extended the tool to support Kotlin source code and more constructs focused on Android applications. Additionally, the framework’s code also suffered considerable refactoring and organization to simplify and ease its comprehension and maintainability. The main effort was to add support for Kotlin source code. The original tool was using the JavaParser888https://javaparser.org (accessed 17 November 2023) library to perform AST analysis of Java code. Since this library is not able to parse Kotlin source code, we integrated JetBrains’s Kotlin compiler999https://github.com/JetBrains/kotlin (accessed 4 June 2023) to perform this task. This required us to implement new versions of the tool’s extractors and visitors classes using the methods provided by the new library to be able to identify contract patterns in Kotlin. We also updated the JavaParser library to support newer Java versions.

The tool is divided into three main parts: 1) usage, which extracts the list of contracts present in each program and produces statistics about their use; 2) inheritance, which identifies contracts in overridden methods and validates whether they violate the Liskov Substitution Principle; and 3) evolution, which analyses how identified contracts evolve in later versions of the application. The following sections describe how each component contributes to answering the proposed research questions.

4.3.2. Usage Study

The usage study is divided in two main steps: 1) identifying contract occurrences and 2) producing statistics about those results. Our tool uses the JavaParser and JetBrains’s Kotlin compiler libraries to perform AST analysis of all dataset’s source code file that is either Java or Kotlin. This analysis is done against a set of extractors to identify occurrences of our defined constructs. Each category requires different approaches for their identification:

  • CREs. During the AST analysis, we look for the pattern:

    if (<condition>) { throw new <exception> (<args>) }

    When this pattern is found, we check whether the exception belongs to the list of CREs considered (see Section 2). In line with Java’s good practices, we assume that CREs are used with preconditions.

  • APIs. Firstly, we check whether the file contains an import declaration to any API package listed in Table 4 . If any is found, all call expressions in that file are analyzed to determine if they are invoking any of the methods provided by the API. As stated in LABEL:paragraph:APIs, we assume the analyzed APIs to be associated with preconditions.

  • Assertions. Identifying Java asserts is straightforward since the JavaParser provides a visitor method for this particular statement. The complexity lies in identifying Kotlin asserts, which is not a reserved keyword. To handle this challenge, when analyzing a file, we first search for any method declaration and any import statement that has a name equal to one of the following expressions: assert, require, requireNotNull, check, checkNotNull. Next, we identify whether the class invokes any method with one of those names. Suppose a class has a method declaration/import statement and an invocation with one of these expression’s names. In that case, we consider it an ambiguous situation, and therefore, we do not consider it an assert instance. If the class invokes one of those methods but does not declare/import any method with that same name, we consider it an assert. This is not a fool-proof approach, but it minimizes the under-reporting to a residual level. We do not classify assertions either as preconditions or postconditions.

  • Annotations. We check if the source code file contains an import statement to one of the packages listed in section 2. If that is the case, we check every annotation in that file to see if it matches any of those provided by the imported package. We also identify the artifact to which the annotation is associated as follows: 1) annotations associated with a method’s parameters are preconditions; 2) annotations associated with a method are postconditions; and 3) annotations associated with a field are class invariants.

  • Others. This category only includes the investigation of the experimental Kotlin Contracts. To identify occurrences of this construct, we look for the pattern - contract {returns (¡condition¿) implies (¡condition¿)}.

Our tool creates a JSON file for each program-version pair that stores the identified contracts, including 1) the file path, 2) the associated condition, 3) the method or property name, 4) the type of artifact (method or property), 5) the line number, and 6) the contract type. In the second step of the usage study, all the JSON files are analyzed to produce statistics about the identified contracts, including the frequency of each category (API, annotation, assertion, etc.), class (preconditions, postconditions, and class invariants), construct (java assert, Guava API, androidx annotations, etc.), to compute the Gini coefficient for each category, and to list the programs with more contracts for each category.

4.3.3. Evolution Study

In the evolution study, we are interested in knowing what happens to contracts while the application evolves. In other words, after identifying a contract in the first version of the application, we check whether, in the later version, the contract still exists, was modified or removed. At the same time, we are also reporting cases when a contract is added to an artifact (method or parameter) in the later version of the app (but was not present in the first version). This information provides insights into how contracts evolve in an application and whether this evolution poses risks to the client.

As already mentioned, a contract establishes rights and obligations for each part — the client assumes that the supplier will keep its obligations and vice-versa. Therefore, when a contract is altered, both parts should be informed and updated accordingly. This is particularly crucial when a precondition is strengthened or when a postcondition is weakened. In the first case, if the precondition is strengthened and the client does not know it, it can fail to cover its new obligations, and, therefore, the supplier is not bound to keep its part of the contract. In the latter case, if the postcondition is weakened, the client may still be making assumptions that the supplier does not ensure anymore. An example is shown in  LABEL:lst:3-evolution-evolution-0, where the annotation @NonNull was added to the toolbar parameter in the last version. This is the case of a precondition strengthening: in the first version, the method accepted a null toolbar, but now it requires it to be not null. Therefore, if the client is not updated, it will fail to cover its new obligation.

1    public static void setToolbarContentColorBasedOnToolbarColor(
2        @NonNull Context context,
3   -        Toolbar toolbar,
4   +        @NonNull Toolbar toolbar,
5        @Nullable Menu menu,
6        int toolbarColor,
7        final @ColorInt int menuWidgetColor
Listing 1: A change in a Java method signature: version 0 specifies three pre-conditions and version 1 specifies four.

To conduct this study, we follow the same approach as Dietrich et al. (Dietrich et al., 2017), firstly creating diff records from the contracts present at the two versions of a program’s method and then classifying them according to the evolution patterns listed in Table 3.

Table 3. Classification of the diff records produced during the evolution and LSP study.
Classification Description Risk
PreconditionsStrengthened

A precondition was added to a method or a clause to an existing precondition with the ’&’ or ’&&’ operators.

Potential risk

PreconditionsWeakened

A precondition was removed from a method, or a clause was added to an existing precondition with the ’—’ or ’——’ operators.

No risk.

PostconditionsStrengthened

A postcondition was added to a method or a clause to an existing postcondition with the ’&’ or ’&&’ operators.

No risk.

PostconditionsWeakened

A postcondition was removed from a method, or a clause was added to an existing postcondition with the ’—’ or ’——’ operators.

Potential risk.

4.3.4. Liskov Substitution Principle Study

When a method is overridden in a subclass, that class can specify new contracts added to the ones inherited from the superclass method. In this case, proper handling of contracts should follow the Liskov Substitution Principle (LSP), which states that the subclass method must accept all input that is valid to the superclass method and meet all guarantees made by the superclass method. In other words, a subclass method can only weaken preconditions and strengthen postconditions.

To detect those occurrences, the analysis tool first lists all methods in each program-version pair associated with their respective class. Additionally, it also identifies the class’ parents. Then, similarly to the evolution study, diff records are created between the subclass and the superclass methods. These records are classified according to the evolution patterns described in Table 3.

5. Results

In this section, we present the results of our empirical study, as well as the main findings.

5.1. RQ1: Contract Usage

Table 4 shows the number of contracts found per category, considering all versions (columns 2 and 3) and considering only the latest version of each application (columns 4 and 5). The table also identifies the number of applications containing at least one contract for that category (columns 6 and 7). The most obvious conclusion is that, in both languages, annotation-based contracts are the most popular category. More specifically, considering both languages in the last version, annotations represent 87.1% of the contracts found, followed by CRE with 9.7%, and then assertions with 2.5%. The results show similar tendencies between Java and Kotlin, and the only difference is that while Java’s second most popular category is CREs, in Kotlin, it is assertions. This relatively high percentage of the assertion category in Kotlin is explained by our inclusion of the four language’s standard library methods listed in Section 2, where require() alone counts 901 total occurrences distributed by 112 last versions.

Finding 1: Most contracts are annotation-based, accounting for 88.31% in Java and 77.44% in Kotlin of the total number of contracts found.

This distribution in categories’ popularity significantly differs from the findings of Dietrich et al. (Dietrich et al., 2017), who reported that the most common category was CREs and found surprisingly low use of annotations. This may be explained by the difference between the datasets’ nature. While our dataset is formed mostly by user-focused Android applications, the author’s dataset was mainly Java libraries. In Table 14, we can also see that most annotations found belong to the androidx.annotation.* package that the authors did not consider since it is Android-specific. Nevertheless, the high number of annotation-based contracts found is in line with literature that supports its increasing popularity (Yu et al., 2021; Grazia and Pradel, 2022).

From Table 4, we also verify that the usage of APIs is very low in both languages, and it is even more residual in Kotlin applications, where only nine instances were found in the latest versions. The known industry skepticism around adding third-party dependencies to projects, which may lead to maintainability and support issues in the future, may explain this finding (Backes et al., 2016; Wang et al., 2020).

Finding 2: The use of APIs to specify contracts is very rare.

Table 4. Number of contracts found in the dataset by category.
contracts (all ver.) contracts (2nd ver.) applications
Category Java Kotlin Java Kotlin Java Kotlin
API 1,813 10 1,121 9 24 4
annotation 158,400 24,125 139,507 15,068 1,097 541
assertion 3,525 3,746 2,186 2,239 326 232
CRE 26,061 3,292 15,150 2,139 789 287
other - 1 - 1 - 1

In Table 14, we have a more detailed perspective by having the frequency of each construct. Firstly, we again highlight that the high number of annotations found is leveraged mostly by the androidx.annotation.* package. In APIs, the Guava library constitutes most of the usage. We were not expecting to see any usage of Spring Framework Asserts since this library was designed to be used in the Spring framework, but we still found one occurrence. At the same time, we found no occurrences of the now deprecated FindBugs annotations. Additionally, we identified a single occurrence of Kotlin Contracts, which may depict the practitioner’s distrust of using a feature still in an experimental phase.

We now consider Table 5, which presents each category’s computed Gini coefficient. The Gini coefficient measures the inequality among the values of a frequency distribution.

Table 5. Gini coefficient by category.
Category Java Kotlin
assertion 0.70 0.71
API 0.80 0.37
annotation 0.88 0.76
CRE 0.77 0.67
others - 1.00

In other words, a Gini coefficient of 0 indicates perfect equality, where all applications have the same number of contracts. In contrast, a Gini coefficient of 1 means that a single program has all the contracts. We observe that all coefficients in the table are higher than 0.50, except for Kotlin’s API usage. The fact that almost all coefficients are very high (close to 1) means that although some applications use contracts intensively, the majority does not use them significantly. This aligns with the results found by Dietrich et al. (Dietrich et al., 2017). This conclusion can also be seen in Table 7, where the five projects that use more contracts per category are listed. We find that a small group of projects own a large percentage of the overall use in each category. Additionally, it is clearly visible from the assertion and CRE categories that the numbers quickly decrease through the first to the fifth application showing the unbalanced usage between applications.

Finding 3: Although there are some applications that use contracts intensively, the majority do not use them significantly.

Table 6. Number of contracts found in the dataset by construct and category.
contracts (all ver.) contracts (2nd ver.)
Construct Category Java Kotlin Java Kotlin
cond. runtime exc. CRE 25,565 3,232 14,887 2,071
unsupp. op. exc. CRE 511 142 308 116
java assert assertion 3,525 - 2,217 -
kotlin assert assertion - 3,868 - 2,370
guava precond. API 1,798 10 1,121 9
commons validate API 148 0 3 0
spring assert API 1 0 1 0
JSR303, JSR349 annotation 0 0 0 0
JSR305 annotation 4,195 20 2,133 13
findbugs annotation 0 0 0 0
jetbrains annotation 2,310 138 1,596 98
android annotation 12,003 5,704 7,013 3,414
androidx annotation 139,933 20,593 86,212 13,811
kotlin contracts others - 1 - 1
Table 7. Top five applications using contracts (second versions only) by category.
Category Applications

assertion

K1rakishou-Kuroba-Experimental (378), a-pavlov-jed2k (314), abhijitvalluri-fitnotifications (143), thundernest-k-9 (114), mozilla-mobile-firefox-android-klar (95)

CRE

redfish64-TinyTravelTracker (1,036), nikita36078-J2ME-Loader (690), abhijitvalluri-fitnotifications (561), lz233-unvcode-android (561), cmeng-git-atalk-android (447)

API

wbaumann-SmartReceiptsLibrary (534), alexcustos-linkasanote (318), BrandroidTools-OpenExplorer (69), oshepherd-Impeller (33), MovingBlocks-DestinationSol (30), inputmice-lttrs-android (24)

annotation

MuntashirAkon-AppManager (5,957), Forkgram-TelegramAndroid (5,552), Telegram-FOSS-Team-Telegram-FOSS (5,549), MarcusWolschon-osmeditor4android (4,393), NekoX-Dev-NekoX (4,032)

other

zhanghai-MaterialFiles (1)

Lastly, Table 8 presents the frequency of each contract type. Once again, we have distinct results for Java and Kotlin. In Java, we found 63.73% of the classified instances in the last versions to be preconditions, 23.19% postconditions, and only 13.08% class invariants. These results align with other studies on contracts (Chalin, 2006; Schiller et al., 2014; Dietrich et al., 2017) that show a clear preference towards preconditions. However, Kotlin’s results are different from this expected preference hierarchy. From the classified instances of the last versions, we found 38.82% to be postconditions, 31.73% class invariants, and 29.44% preconditions. This suggests that Kotlin developers tend to favor postconditions over any other type, while preconditions come at the last position. Also, according to the classification described in Section 4.3.2, in our study, only annotations may be classified as postconditions or class-invariants. This means that in Kotlin, there is a higher number of annotations associated with the method’s return values and class properties than with the method’s parameters.

Finding 4: Java and Kotlin practitioners display different tendencies when it comes to the contract type. In Java, there is a clear preference towards preconditions, while in Kotlin, postconditions are the most frequent type.

Table 8. Number of contracts found in the dataset by type.
contracts (all ver.) contracts (2nd ver.) applications
Type Java Kotlin Java Kotlin Java Kotlin
pre-condition 120,671 9,203 72,160 5,744 989 349
post-condition 41,764 11,490 26,253 7,575 829 435
invariants 23,836 9,122 14,811 6,190 643 348
not classified 3,584 3,893 2,267 2,394 279 202

Although we can not provide a reason for this finding with certainty, we argue that the difference in practitioners’ preference for each type reported in Table 8 could stem from different behavior patterns which are demonstrated in Table 9 and Table 10. These tables list the ten most occurring constructs for each type in the last versions of Java and Kotlin applications. To create these tables, we followed the classification described in Section 4.3.2; hence, for example, although there are 2,217 instances of JavaAssert in Java, these were not included in the list since the analysis tool does not classify asserts by type.

By comparing the two tables, we draw distinct behavior patterns between the two languages. In the Kotlin constructs reported by Table 10, none of the top ten most popular constructs relates to null-checking. But in Java’s instances, reported in Table 9, 82.03% of preconditions and 71.12% of postconditions are associated with null-checking. In this number, we are not considering potential CREIllegalArgumentException and CREIllegalStateException that could be associated with null-checking since this would require analyzing the condition present at the if-statement. This confirms a lack of expressiveness in the contracts specified by Java practitioners, with most being associated with null-checking, which aligns with previous studies (Schiller et al., 2014; Estler et al., 2014).

This contrast in null-checking contracts between Java and Kotlin is easily explained by the languages’ different takes on nullability. In Kotlin, contrary to Java, regular types are non-nullable by default; therefore, in most cases, practitioners do not have the need for constructs like AndroidXNonNull or JSR305NonNull. On the other hand, it is interesting to observe that relaxing this constraint to allow nullable types is not a common practice since we found no meaningful use of constraints like AndroidXNullable and similar in Kotlin.

Finding 5: In Java applications’ last versions, at least 77.72% of preconditions, 65.63% of postconditions, and 61.24% of class invariants are related to null-checking. In the case of Kotlin, we found only about 3.12% of preconditions, 6.00% of postconditions, and 0.57% of class invariants to be performing null-checking.

Table 9. The top 10 most frequent constructs per type in the last versions of Java applications.
Pre-conditions Post-conditions
AndroidXNonNull (36,031) AndroidXNonNull (10,359)
AndroidXNullable (14,983) AndroidXNullable (5,954)
CREIllegalArgumentException (7,663) AndroidSuppressLint (3,125)
CREIllegalStateException (3,232) AndroidTargetApi (1,243)
CRENullPointerException (2,230) AndroidXRequiresApi (732)
GuavaPreconditionNotNull (1,021) AndroidXWorkerThread (551)
JSR305NonNull (860) AndroidXKeep (398)
AndroidXStringRes (660) AndroidXCallSuper (380)
CREIndexOutOfBoundsException (656) AndroidXUiThread (326)
JetBrainsNotNull (612) JSR305NonNull(322)
Table 10. The top 10 most frequent constructs per type in the last versions of Kotlin applications.
Pre-conditions Post-conditions
AndroidXStringRes (1,142) AndroidSuppressLint (2,289)
CREIllegalStateException (772) AndroidXVisibleForTesting (1,663)
CREIllegalArgumentException (748) AndroidXRequiresApi (720)
AndroidXColorInt (523) AndroidXWorkerThread (638)
AndroidXDrawableRes (425) AndroidXMainThread (441)
AndroidXAttrRes (255) AndroidXCallSuper (319)
AndroidXColorRes (195) AndroidXColorInt (237)
AndroidXIdRes (184) AndroidTargetApi (205)
UCREUnsupportedOperationException (116) AndroidXUiThread (195)
AndroidXFloatRange (80) AndroidXAnyThread(184)

5.2. RQ2: Evolution

Table 11 presents the number of contracts in the first and second versions by category. In general, for most cases, the number of contracts in each category increased from the first to the last version. The only categories where the number decreased were the Apache’s Commons Validate and in JSR305 annotations package for Java. The decrease in JSR305 usage could be explained by it currently being in a dormant status, or in other words, with no activity since 2017.

Table 11. Contract elements by type in the first and last version.
contracts (1st vers.) contracts (2nd vers.)
Type category Java Kotlin Java Kotlin
cond. runtime exc. CRE 10,678 1,161 14,887 2,071
unsupp. op. exc. CRE 203 26 308 116
java assert assertion 1,308 - 2,217 -
kotlin assert assertion - 1,498 - 2,370
guava precond. API 677 1 1,121 9
commons validate API 11 0 3 0
spring assert API 0 0 1 0
JSR303, JSR349 annotation 0 0 0 0
JSR305 annotation 2,062 7 1,133 13
findbugs annotation 0 0 0 0
jetbrains annotation 714 40 1,596 98
android annotation 4,990 2,290 7,013 3,414
androidx annotation 53,721 6,782 86,212 13,811
kotlin contracts other - 0 - 1

We computed some metrics to understand how the increase in the program’s size relates to the number of contracts. Those metrics, listed in Table 12, include the average and median values for the number of methods, the number of contracts, and the ratio between both (for the first and second versions). The table shows that there is an average increase of about 109.936 methods per program. This is expected since the program’s size tends to increase from the first to the second version. However, a more interesting insight comes from the contracts count. Although the average number of contracts per program increased, its median value decreased. This means that the dataset includes outliers with a significant rise in contract usage that considerably affected the average value. To confirm this data, we computed the ratio between the number of contracts and the number of methods for each version of a program. Then, we computed the difference between the second and the first version’s ratio for each program. The average of these differences is -0.0057, and the median is -0.0012. Although the values are very small, we conclude that the number of methods increases significantly more than the number of contracts.

Table 12. Average and median number of methods, contracts, and their ratio for the two versions.
1st version 2nd version
Metric Median Average Median Average
methods count 310 972,037 356 1,081,973
contracts count 7 64,938 6 79,658
contract-to-method ratio 0.029 0.061 0.022 0.055

Finding 6: Applications that use contracts continue to use them in later versions. Moreover, the total and average numbers of contracts increase, but its median decreases by a small factor. Also, the number of methods increases at a higher rate than the number of contracts.

Similarly to our study, Dietrich et al. (Dietrich et al., 2017) also found that the median value of the ratio does not change much. Still, while we reported a decline between both versions (0.029 to 0.022), they found a rise (0.021 to 0.023). This means that although both studies show general stability related to contracts usage, contrary to their study, we were not able to find a positive correlation between the increase in the number of methods and in the number of contracts.

5.3. RQ3: Safety

To address whether practitioners tend to misuse contracts in either program evolution or inheritance contexts, we build diff records to be classified according to evolution patterns. Some of these evolution patterns are associated with a potential risk that may lead to client breaks, namely when preconditions are strengthened or postconditions are weakened. This process was described in more detail in Sections 4.3.3 and 4.3.4. It is important to note that the analysis tool cannot precisely capture all contract changes due to the variety of constructs we are analyzing and the complexity of their semantics. This can potentially lead to under-reporting. Even so, Table 13 still provides valuable insights into the safety of contract usage and evolution. The table shows the frequency of each evolution pattern in the context of program evolution (third column). At first glance, we may be rushed to conclude that specifications are generally stable since the most frequent pattern is when a contract remains unchanged from the first to the second version. Unfortunately, this is not true since the occurrences of contract changes make up more than 50% of the patterns found. Still, overall, most of the changes are non-critical ones — including minor changes, preconditions weakening, and postconditions strengthening — which is a positive finding. Less optimistic is that the second most common pattern is the case of preconditions strengthening, one of the two cases that potentially offers risk. In summary, although many contracts remain unchanged and most changes are not critical, we still found many occurrences that can lead to potential breaks.

Finding 7: There are instances of unsafe contract changes while the program evolves, particularly cases of preconditions strengthening.

Finally, Table 13 also presents the results found for evolution patterns in the context of inheritance (fourth column). We observe that the preconditions strengthening pattern makes up almost 50% of classified instances. We also note that from the classified instances, most parts are related to contract changes which means a lack of stability in specifications. Both in the evolution and the inheritance study, we found low occurrences of postconditions weakening when compared to the other classifications. Also, compared to the reports from Dietrich et al.’s study (Dietrich et al., 2017), our results indicate a greater ratio of preconditions strengthening per preconditions found.

Finding 8: There are instances of unsafe contract changes in an overriding context that violate the Liskov Substitution Principle, particularly cases of preconditions strengthening.

Table 13. Contract evolution in the context of program evolution and inheritance.
Contract Evolution Critical Evolution (#) Inheritance (#)
unchanged no 32,070 158
minor change no 199 1
pre-conditions weakened no 13,870 3
post-conditions strengthened no 9,906 71
pre-conditions strengthened yes 20,870 232
post-conditions weakened yes 5,461 0
unclassified ? 8,307 145

6. Discussion

In this section, we answer the research questions listed in LABEL:sec:research-questions, we discuss the practical implications of our findings, and we outline potential threats to the validity of our work.

6.1. Answers to Research Questions

Given the findings reported in the previous section, we answer the research questions posed in LABEL:sec:research-questions as follows:

RQ1 [Contract Usage] How and to what extent are contracts used in Android applications? Contracts are concentrated in a small number of applications. Still, when applications use contracts, annotation-based approaches are the most frequent, with the androidx.annotation package being the most popular. The use of APIs to specify contracts is rare. This study found differences between the two languages. While in Java, 63.73% of the classified instances are preconditions, Kotlin programs display a more equally distributed selection with 38.82% postconditions at the top. We also found that more than 50% of the classified contracts in Java are related to null-checking, while in Kotlin that number is smaller than 10%.

RQ2 [Evolution] How does contract usage evolve in an application? Applications that use contracts continue to use them in later versions. When comparing the number of contracts in both versions of the applications, on average, the number of contracts increases. Still, this is caused by some outliers that increase its usage intensively, driving up the average. In fact, the median value decreases. Furthermore, the contract-to-method ratio decreases between versions — a median decrease of -0.0057 and an average decrease of -0.0012. Although by a residual factor, we observed that the number of contracts declines as the program grew.

RQ3 [Safety] Are contracts used safely in the context of program evolution and inheritance? We found that contract changes are frequent, meaning a lack of specifications’ stability. From those changes, preconditions strengthening is the most classified pattern. These results clearly show a potentially unsafe use of contracts that may lead to client breaks.

6.2. Practical Implications & Recommendations

From the findings presented in this work, we are able to derive practical implications and recommendations.

Recommendation 1: Due to the visible fragmentation of technologies and approaches to specifying contracts, both Java and Kotlin standard libraries should be equipped with specialized constructs to specify contracts and with proper official documentation.

Recommendation 2: It would be desirable to have libraries that standardize contract specifications in Java and Kotlin. Our results suggest that these libraries should be built around annotation-based contracts, given its popularity among practitioners. An annotation-based approach, where specifications are added to the program as metadata, is similar to Eiffel’s approach, where the assertions do not obfuscate the method’s implementation. This recommendation also applies to tool builders: given that the current use of APIs in Android development appears to be relatively low, analysis tools for Android that leverage contracts should prioritize annotations.

Recommendation 3: New tools to aid practitioners writing contracts would be very valuable. For example, the integration into IDEs of contract suggestion features supported by tools for invariant inference, such as Daikon (Ernst et al., 2007), could help increase practitioners’ use of contracts. Another contribution could be IDE and continuous integration plugins to detect contract violations in the context of program evolution and inheritance.

Recommendation 4: Our findings show that Kotlin’s default non-nullable types reduce the need to explicitly write some contracts, highlighting the significance of language design features that enable safety by default. These findings are relevant for the design of programming languages and can serve as motivation for practitioners when selecting programming languages for new projects.

6.3. Threats to Validity

Internal Validity. The accuracy of our results depends on the quality and correctness of the artifact and there may exist bugs in the code. To mitigate this, we extensively tested the tool. In addition, all code and datasets used are publicly available for other researchers and potential users to check the validity of the results. External Validity. The projects that we selected might not be an accurate representation of other, more popular, Android app stores. We mitigated this by using F-Droid, a collection of open-source applications commonly used in other research studies. We also mitigated this risk by analysing all the projects that satisfy the inclusion criteria, leading to a substantial dataset (51 MLoC) with applications of different types. Conclusion Validity. We might have missed language constructs that could be used to specify contracts. To mitigate this, we followed an established taxonomy (Dietrich et al., 2017) that we adapted and extended by systematically searching forums and the official Android documentation. Also, all our code is easily open to extension. Another risk comes from the fact that our dataset is imbalanced (with more Java than Kotlin applications). We mitigate this by explicitly discussing this imbalance when presenting results that might be affected by it.

7. Conclusions

Additional empirical evidence about what types of constructs and language features are used by practitioners to represent contracts can help the software engineering community create or improve existing libraries and tools to increase DbC adoption. This knowledge also serves practitioners to understand DbC’s current practices better, hel** them discover and decide between different implementation approaches of this technique for their projects. Other researchers can also use this data to draw additional studies and to foster further discussion along with the increasing interest in these empirical studies about language features.

Future work includes studies with practitioners to understand the challenges faced when specifying contracts, the use of annotations to improve Android analysis tools (Ribeiro et al., 2021; Pereira et al., 2022), and the development of tools that can help increase the adoption of DbC (Álvaro Silva et al., 2024).

References

  • (1)
  • A.Feldman et al. (2006) Y. A.Feldman, O. Barzilay, and S. Tyszberowicz. 2006. Jose: aspects for design by contract. In Fourth IEEE International Conference on Software Engineering and Formal Methods. Los Alamitos, CA, USA.
  • Algarni and Magel (2018) A. Algarni and K. Magel. 2018. Toward Design-by-Contract Based Generative Tool for Object-Oriented System. In 2018 IEEE 9th International Conference on Software Engineering and Service Science (ICSESS). Proceedings. Piscataway, NJ, USA, 168 – 73.
  • Aniche (2022) M. Aniche. 2022. Effective Software Testing. A Developer’s Guide. Manning, Shelter Islands.
  • Backes et al. (2016) M. Backes, S. Bugiel, and E. Derr. 2016. Reliable Third-Party Library Detection in Android and Its Security Applications. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security (CCS ’16). Association for Computing Machinery, 356–367.
  • Bloch (2008) Joshua Bloch. 2008. Effective java (2nd ed.). Addison-Wesley Professional.
  • Blom et al. (2002a) M. Blom, E.J. Nordby, and A. Brunstrom. 2002a. An experimental evaluation of programming by contract. In Proceedings Ninth Annual IEEE International Conference and Workshop on the Engineering of Computer-Based Systems. 118–127.
  • Blom et al. (2002b) M. Blom, E. J. Nordby, and A. Brunstrom. 2002b. On the relation between design contracts and errors: a software development strategy. In Proceedings Ninth Annual IEEE International Conference and Workshop on the Engineering of Computer-Based Systems. 110–117.
  • Casalnuovo et al. (2015) C. Casalnuovo, P. Devanbu, A. Oliveira, V. Filkov, and B. Ray. 2015. Assert Use in GitHub Projects. In 2015 IEEE/ACM 37th IEEE International Conference on Software Engineering (ICSE). Proceedings, Vol. 1. Los Alamitos, CA, USA, 755 – 66.
  • Chalin (2006) P. Chalin. 2006. Are practitioners writing contracts? Springer, Berlin, Germany, 100 – 113.
  • Chen et al. (2019) Sen Chen, Lingling Fan, Chunyang Chen, Ting Su, Wenhe Li, Yang Liu, and Lihua Xu. 2019. Storydroid: Automated generation of storyboard for Android apps. In 2019 IEEE/ACM 41st International Conference on Software Engineering (ICSE). IEEE, 596–607.
  • Counsell et al. (2017) S. Counsell, T. Hall, T. Shippey, D. Bowes, A. Tahir, and S. MacDonell. 2017. Assert Use and Defectiveness in Industrial Code. In Proceedings of the IEEE International Symposium on Software Reliability Engineering Workshops. 20–23.
  • Dietrich et al. (2017) J. Dietrich, D. J. Pearce, K. Jezek, and P. Brada. 2017. Contracts in the Wild: A Study of Java Programs. In 31st European Conference on Object-Oriented Programming (ECOOP 2017) (Leibniz International Proceedings in Informatics (LIPIcs), Vol. 74). Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik, Dagstuhl, Germany, 9:1–9:29.
  • Ernst et al. (2007) Michael D Ernst, Jeff H Perkins, Philip J Guo, Stephen McCamant, Carlos Pacheco, Matthew S Tschantz, and Chen Xiao. 2007. The Daikon system for dynamic detection of likely invariants. Sci. Comput. Program. 69, 1-3 (2007), 35–45.
  • Estler et al. (2014) H.-C. Estler, C. A. Furia, M. Nordio, M. Piccioni, and B. Meyer. 2014. Contracts in Practice. In FM 2014: Formal Methods. 19th International Symposium. Proceedings: LNCS 8442. Cham, Switzerland, 230 – 46.
  • Fairbanks (2019) G. Fairbanks. 2019. Better code reviews with design by contract. IEEE Software 36, 6 (2019), 53 – 6.
  • Grazia and Pradel (2022) L. Di Grazia and M. Pradel. 2022. The Evolution of Type Annotations in Python: An Empirical Study. In Proceedings of the 30th ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering (Singapore, Singapore). Association for Computing Machinery, New York, NY, USA, 209–220.
  • Hollunder et al. (2012) B. Hollunder, M. Herrmann, and A. Hülzenbecher. 2012. Design by Contract for Web Services: Architecture, Guidelines, and Map**s. In International Journal on Advances in Software, Vol. 5.
  • Kochhar and Lo (2017) P. Kochhar and D. Lo. 2017. Revisiting Assert Use in GitHub Projects. In Proceedings of the 21st International Conference on Evaluation and Assessment in Software Engineering. 298–307.
  • Kudrjavets et al. (2006) Gunnar Kudrjavets, Nachiappan Nagappan, and Thomas Ball. 2006. Assessing the Relationship between Software Assertions and Faults: An Empirical Investigation. In 2006 17th International Symposium on Software Reliability Engineering. 204–212.
  • Meyer (1988) Bertrand Meyer. 1988. Programming as contracting. Advances in Object-Oriented Software Engineering (1988), 1–15.
  • Meyer (1992) B. Meyer. 1992. Applying ‘design by contract’. Computer 25, 10 (1992), 40 – 51.
  • Murthy (2018) P. V. R. Murthy. 2018. Design by contract methodology. In 2018 International Conference on Advances in Computing, Communications and Informatics (ICACCI). Piscataway, NJ, USA, 482 – 8.
  • Naumchev (2019) A. Naumchev. 2019. Seamless Object-Oriented Requirements. In 2019 International Multi-Conference on Engineering, Computer and Information Sciences (SIBIRCON). Proceedings. Piscataway, NJ, USA.
  • Pereira et al. (2022) Ricardo B Pereira, João F. Ferreira, Alexandra Mendes, and Rui Abreu. 2022. Extending Ecoandroid with automated detection of resource leaks. In Proceedings of the 9th IEEE/ACM International Conference on Mobile Software Engineering and Systems. 17–27.
  • Ribeiro et al. (2021) Ana Ribeiro, João F. Ferreira, and Alexandra Mendes. 2021. Ecoandroid: An Android studio plugin for develo** energy-efficient Java mobile applications. In 2021 IEEE 21st International Conference on Software Quality, Reliability and Security (QRS). IEEE, 62–69.
  • Schiller et al. (2014) T. W. Schiller, K. Donohue, F. Coward, and M. D. Ernst. 2014. Case Studies and Tools for Contract Specifications. In Proceedings of the 36th International Conference on Software Engineering (Hyderabad, India) (ICSE 2014). Association for Computing Machinery, New York, NY, USA, 596–607.
  • Silva et al. (2020) C. Silva, S. Guerin, R. Mazo, and J. Champeau. 2020. Contract-based design patterns: a design by contract approach to specify security patterns. In ARES 2020: Proceedings of the 15th International Conference on Availability, Reliability and Security. New York, NY, USA.
  • Stats (2023) StatCounter Global Stats. 2023. Operating System Market Share Worldwide. https://gs.statcounter.com/os-market-share#monthly-202208-202209-bar [Online; accessed 3-February-2023].
  • Tantivongsathaporn and Stearns (2006) J. Tantivongsathaporn and D. Stearns. 2006. An experience with design by contract. In 2006 13th Asia Pacific Software Engineering Conference (APSEC’06). Piscataway, NJ, USA, 327 – 33.
  • Tao and Edmunds (2018) K. Tao and P. Edmunds. 2018. Mobile APPs and Global Markets. Theoretical Economics Letters 08 (01 2018), 1510–1524.
  • Wang et al. (2020) Y. Wang, B. Chen, K. Huang, B. Shi, C. Xu, X. Peng, Y. Wu, and Y. Liu. 2020. An Empirical Study of Usages, Updates and Risks of Third-Party Libraries in Java Projects. In 2020 IEEE International Conference on Software Maintenance and Evolution (ICSME). 35–45.
  • Wei et al. (2011) Y. Wei, C.A. Furia, N. Kazmin, and B. Meyer. 2011. Inferring better contracts. In 2011 33rd International Conference on Software Engineering (ICSE 2011). Piscataway, NJ, USA, 191 – 200.
  • Yu et al. (2021) Z. Yu, C. Bai, L. Seinturier, and M. Monperrus. 2021. Characterizing the Usage, Evolution and Impact of Java Annotations in Practice. IEEE Transactions on Software Engineering 47, 5 (2021), 969–986.
  • Zeng et al. (2019) Yi Zeng, **fu Chen, Weiyi Shang, and Tse-Hsun Chen. 2019. Studying the characteristics of logging practices in mobile apps: a case study on f-droid. Empirical Software Engineering 24 (2019), 3394–3434.
  • Zhou et al. (2017) Y. Zhou, P. Pelliccione, J. Haraldsson, and M. Islam. 2017. Improving Robustness of AUTOSAR Software Components with Design by Contract: A Study Within Volvo AB. In Software Engineering for Resilient Systems. 9th International Workshop, SERENE 2017. Proceedings: LNCS 10479. Cham, Switzerland, 151 – 68.
  • Álvaro Silva et al. (2024) Álvaro Silva, Alexandra Mendes, and João F. Ferreira. 2024. Leveraging Large Language Models to Boost Dafny’s Developers Productivity. arXiv:2401.00963 [cs.SE]

Supplementary Material

The following appendices contain additional material that complements the main body of the paper.

Appendix A Information about Available Data / Artifacts

All the code and datasets are publicly available at https://figshare.com/s/d6eb7e5522bb535dc81a

Alternatively, the following links can also be used:

Appendix B Dataset: GitHub Statistics

Figure 1 shows the distribution of GitHub-related metrics — including the number of contributors, stars, watchers, and forks — for the projects that form the evaluated dataset. While the number of contributors describes the project’s team and its size, the number of stars, watchers, and forks help to assess each project’s popularity and relevance among other developers. For reference, the maximum outlier for each metric is 1682 for watchers, 33,689 for stars, 11,633 for forks, and 398 for contributors. Again, this diversity ensures the quality of the dataset and reduces potential bias.

Refer to caption
(a) Number of contributors.
Refer to caption
(b) Number of stars.
Refer to caption
(c) Number of watchers.
Refer to caption
(d) Number of forks.
Figure 1. The distribution of GitHub repositories-related metrics for the dataset’s projects, without outliers.

Appendix C Contracts in Android Applications

C.1. CREs

An exception can be used to signal, at runtime, a contract violation. However, it is important to note that the exception itself does not represent a contract; it needs to be associated with a previous check — for example, an exception thrown inside an if-else block — to be considered so. Java and Kotlin offer many exceptions that can be used for this purpose, such as the IllegalArgumentException. The android.util package offers additional exceptions that we are also interested in analyzing, such as the case of the AndroidRuntimeException. We are also interested in a particular exception, the UnsupportedOperationException, which, according to the Java documentation, is thrown to indicate that the requested operation is not supported. An example of this is the following method proceedWithCheckout, which states that it can only perform its task when the shop**Cart has at least one item:

  public void proceedWithCheckout(List<Item> shop**Cart)  {
    if (shop**Cart.isEmpty()) {
      throw new IllegalArgumentException();
    }
    ...
  }

C.2. APIs

The methods provided by the Validate class are simply wrap** exceptions that we have already considered in the CREs. Still, as we can see in the following Kotlin code snippet, this API contributes to cleaner code compared to a raw CRE-based solution since we can specify the contracts in a single line and with meaningful wording. In particular, we specify a precondition stating that the items list is not empty:

  fun addToShop**Cart(items: List<Item>): List<Item> {
    Validate.notEmpty(items)
    shop**Cart.addAll(items)
    return shop**Cart
  }

Appendix D Algorithm for diff records of the contracts

The algorithm to create diff records is illustrated in Figure 2. The algorithm to create diff records consists of walking through each contract identified in the usage study (steps 1 and 2). We create a unique index for each contract in the loop to ensure we are not double-counting occurrences (steps 3 and 4). If the contract was not analyzed yet, we determine whether the contract belongs to the first version of the application (step 5). If this is the case, we create a diff record by retrieving all the contracts in both versions of this contract’s method (steps 5.a and 5.b). Otherwise, if the contract belongs to the last version of the application (step 6), we determine whether the associated method already existed in the first version (step 7). If the method existed and its first version did not contain contracts, we create a diff record with only the last version’s contracts (steps 8 and 9). If the first version contains contracts, we do not create a diff record to not double-count contracts since they will be reported by step 5.b. Ultimately, the program outputs the diff records created for each program-version method (step 10).

Refer to caption
Figure 2. An overview of the algorithm to create diff records of the contracts found in two versions of a method.

Appendix E Liskov Substitution Principle Study: Example

LABEL:lst:3-evolution-inheritance-1 shows the TagEntry class that extends the EntryItem class. It also overrides the setName method inherited from its parent class. Note that while the superclass implementation contains no contract, the subclass implementation adds a CRE precondition throwing an IllegalStateException when the id property does not end with the name parameter. Therefore, we are in the presence of a precondition strengthening in the context of inheritance, i.e., a violation of the Liskov Substitution Principle.


1public class EntryItem {
2  public void setName(String name) {
3    if (name != null) {
4      this.name = name;
5      this.normalizedName = StringNormalizer.normalizeWithResult(this.name, false);
6    } else {
7      this.name = "null";
8      this.normalizedName = null;
9    }
10  }
11}
12
13public class TagEntry extends EntryItem {
14  public final String id;
15
16  @Override
17  public void setName(String name) {
18    if (name != null) {
19      if (!id.endsWith(name))
20        throw new IllegalStateException("The display name and tag name need to be equal.");
21      super.setName(name);
22    } else {
23      super.setName(id.substring(SCHEME.length()));
24    }
25  }
26}
Listing 2: A Java class that overrides the setName method from its parent class. This is an example of pre-condition strengthening in the context of inheritance, i.e., a violation of the Liskov Substitution Principle.

Appendix F Finding 4: Top 100 Applications

Regarding Finding 4, we have also looked into whether there was any relation between the number of contracts in the last version and any of the GitHub metrics from Figure 1. However, no meaningful correlation was found.

Refer to caption
Refer to caption
Figure 3. Comparison of the distribution of the identified contract types in the top 100 applications with higher usage per type for Java and Kotlin.

Appendix G Table 6 extended

In this section, we present a version of Table 6 (in the submitted paper) that also includes the number of applications (last two columns).

Table 14. Number of contracts found in the dataset by construct and category.
contracts (all ver.) contracts (2nd ver.) applications
Construct Category Java Kotlin Java Kotlin Java Kotlin
cond. runtime exc. CRE 25,565 3,232 14,887 2,071 779 285
unsupp. op. exc. CRE 511 142 308 116 97 27
java assert assertion 3,525 - 2,217 - 325 -
kotlin assert assertion - 3,868 - 2,370 - 234
guava precond. API 1,798 10 1,121 9 22 4
commons validate API 148 0 3 0 1 0
spring assert API 1 0 1 0 1 0
JSR303, JSR349 annotation 0 0 0 0 0 0
JSR305 annotation 4,195 20 2,133 13 40 4
findbugs annotation 0 0 0 0 0 0
jetbrains annotation 2,310 138 1,596 98 115 20
android annotation 12,003 5,704 7,013 3,414 910 464
androidx annotation 139,933 20,593 86,212 13,811 599 401
kotlin contracts others - 1 - 1 - 1

Appendix H List of Conditional Runtime Exceptions analyzed

Table 15. List of exceptions analyzed in the CRE category.
CREs Constructs
AndroidRuntimeException MissingResourceException
ArithmeticException NegativeArraySizeException
ArrayStoreException NoSuchElementException
ArrayStoreException NullPointerException
BufferOverflowException ParcelFormatException
BufferUnderflowException ParseException
ClassCastException ProviderException
CompletionException ProviderNotFoundException
ConcurrentModificationException RejectedExecutionException
DOMException SQLException
DateTimeException SecurityException
EmptyStackException TypeNotPresentException
EnumConstantNotPresentException UncheckedIOException
FileSystemAlreadyExistsException UndeclaredThrowableException
FileSystemNotFoundException UnsupportedOperationException
IllegalArgumentException WrongMethodTypeException
IllegalMonitorStateException AcceptPendingException
IllegalStateException AccessControlException
IncompleteAnnotationException AlreadyBoundException
IndexOutOfBoundsException AlreadyConnectedException
LSException ArrayIndexOutOfBoundsException
MalformedParameterizedTypeException BadParceableException
MalformedParametersException CancellationException
UnsupportedAddressTypeException UnsupportedCharsetException
WritePendingException ZoneRulesException
CancelledKeyException PatternSyntaxException
ClosedDirectoryStreamException StringIndexOutOfBoundsException
ClosedFileSystemException ReadOnlyBufferException
ClosedFileSystemException ReadOnlyFileSystemException
ClosedSelectorException ReadPendingException
ClosedWatchServiceException ShutdownChannelGroupException
ConnectionPendingException StringIndexOutOfBoundsException
NonReadableChannelException UnknownFormatConversionException
NonWritableChannelException UnknownFormatFlagsException
NotYetBoundException UnresolvedAddressException
NotYetConnectedException UnsupportedTemporalTypeException
NumberFormatException Overlap**FileLockException

Appendix I List of API’s methods analyzed

Table 16. List of the methods analyzed from each API.
Annotations analyzed
Apache lang2 Validate
allElementsOfType()
isTrue()
noNullElements()
notEmpty()
notNull()
Apache lang3 Validate
allElementsOfType()
exclusiveBetween()
inclusiveBetween()
assignableFrom()
isInstanceOf()
matchesPattern()
notBlank()
validIndex()
validState()
Guava Preconditions
checkArgument()
checkState()
checkElementIndex()
checkPositionIndex()
checkNotNull()
checkPositionIndexes()
Spring Assert
doesNotContain()
hasLength()
hasText()
notEmpty()
noNullElements()
isInstanceOf()
isAssignable()
state()
isNull()
isTrue()
notNull()

Appendix J List of Annotations analyzed

Table 17. List of annotations analyzed per package.
Annotations analyzed

JSR305

@CheckForNull @CheckForSigned
@MatchesPattern @Nonnegative
@Nonnull @Nullable
@OverridingMethodsMustInvokeSupper @ParametersAreNonnullByDefault
@RegEx @Signed
@Syntax @Syntax
@Tainted @Untainted
@WillClose @WillCloseWhenClosed
@WillNotClose @Guardedby
@Immutable @NotThreadSafe
@ThreadSafe

JSR303, JSR349

@Null @DecimalMin
@NotNull @Size
@AssertTrue @Digits
@AssertFalse @Past
@Min @Future
@Max @Pattern
@DecimalMax

JetBrain

@Contract @NotNull
@Nullable @PropertyKey
@TestOnly

IntelliJ

@BoxLayoutAxis @CalendarMonth
@CursorType @FlowLayoutAlignment
@FontStyle @HorizontalAlignment
@InputEventMask @ListSelectionMode
@PatternFlags @TabLayoutPolicy
@AdjustableOrientation @Flow
@Identifier @TabPlacement
@TitledBorderJustification @TitledBorderTitlePosition
@Language @MagicConstant
@Pattern @PrintFormat
@PrintFormat @RexExp
@Subst

FindBugs

@CheckForNull @NonNull
@Nullable @PossiblyNull
@FontStyle @HorizontalAlignment
@UnkownNullness @CreateObligation
@DischargesObligation @CleanupObligation

Android

@AndroidSupressLint @AndroidTargetApi

Androidx

@AnimatorRes @AnimRes
@AnyRes @AnyThread
@AnyThread @ArrayRes
@AttrRes @BinderThread
@BinderThread @BoolRes
@CallSuper @CheckResult
@ChecksSdkIntAtLeast @ColorInt
@ColorLong @ColorRes
@ContentView @DimenRes
@Dimension @NotInline
@DrawableRes @FloatRange
@FloatRange @FontRes
@FontRes @FractionRes
@FractionRes @GuardedBy
@GuardedBy @HalfFloat
@IdRes @InspectableProperty
@IntDef @IntegerRes
@InterpolatorRes @IntRange
@Keep @LayoutRes
@LongDef @MainThread
@MainThread @MenuRes
@NavigationRes @NonNull
@Nullable @PluralsRec
@Px @RawRes
@RequiresApi @RequiresFeature
@RequiresPermission @RestrictTo
@Size @StringDef
@StringRes @StyleableRes
@StyleRes @TransitionRes
@UiThread @VisibleForTesting
@WorkerThread @XmlRes