Duncan McGregor & Nat Pryce Java to Kotlin A Refactoring Guidebook
(This page has no text content)
Praise for Java to Kotlin Nat and Duncan open the book by saying they wish this book had existed when they adopted Kotlin. I completely agree. The book takes their experiences, their trials and errors, and helps you, the reader, to be on the right path. It definitely is a book that should be on anyone’s shelf coming to Kotlin from the JVM ecosystem. An enjoyable read, and an even better reference guide! —Hadi Hariri, JetBrains To take familiar good old Java code and see it transform, incrementally, into concise, clear, expressive and easy to maintain Kotlin code is a wonderful way to learn the language. Benefit from the experience McGregor and Pryce have distilled in this book. —Dr. Venkat Subramaniam, award-winning author and founder of Agile Developer, Inc. The fastest way to upgrade your skills from Java to Kotlin. Essential reading for any professional Java developer. —Dawn Griffiths and David Griffiths, authors of Head First Kotlin
(This page has no text content)
Duncan McGregor and Nat Pryce Java to Kotlin A Refactoring Guidebook Boston Farnham Sebastopol TokyoBeijing
978-1-492-08227-9 [LSI] Java to Kotlin by Duncan McGregor and Nat Pryce Copyright © 2021 Duncan McGregor and Nat Pryce. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Suzanne McQuade Development Editor: Sarah Grey Production Editor: Kate Galloway Copyeditor: nSight, Inc. Proofreader: Sonia Saruba Indexer: Judith McConville Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea August 2021: First Edition Revision History for the First Edition 2021-08-13: First Release See http://oreilly.com/catalog/errata.csp?isbn=9781492082279 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Java to Kotlin, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the authors, and do not represent the publisher’s views. While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
Table of Contents Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi 1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 The Grain of a Programming Language 1 An Opinionated History of Java Programming Style 4 The Grain of Kotlin 9 Refactoring to Kotlin 11 What Are We Working On? 13 Let’s Get Started! 14 2. Java to Kotlin Projects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Strategy 15 Adding Kotlin Support to a Java Build 17 Moving On 20 3. Java to Kotlin Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 A Simple Value Type 21 The Limitations of Data Classes 28 Moving On 32 4. Optional to Nullable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Representing Absence 35 Refactoring from Optional to Nullable 37 Refactoring to Idiomatic Kotlin 44 Moving On 50 v
5. Beans to Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Beans 51 Values 52 Why Should We Prefer Values? 53 Refactoring Beans to Values 53 Moving On 60 6. Java to Kotlin Collections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 Java Collections 61 Kotlin Collections 64 Refactoring from Java to Kotlin Collections 67 Moving On 75 7. Actions to Calculations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Functions 77 Calculations 78 Actions 79 Why Should We Care? 80 Why Prefer Calculations? 81 Refactoring Actions to Calculations 82 Moving On 93 8. Static Methods to Top-Level Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Java Statics 95 Kotlin Top-Level Functions, Objects, and Companions 97 Refactoring from Static Methods to Top-Level Functions 98 Move to Top Level 101 Kotlinify 103 Moving On 105 9. Multi- to Single-Expression Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Take 1: Inlining 109 Take 2: Introduce a Function 110 Take 3: Let 114 Take 4: Stepping Back 116 Moving On 119 10. Functions to Extension Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Functions and Methods 121 Extension Functions 123 Extensions and Function Types 124 vi | Table of Contents
Extension Properties 125 Conversions 125 Nullable Parameters 127 Nullable Receivers 128 Generics 130 Extension Functions as Methods 131 Refactoring to Extension Functions 131 Moving On 145 11. Methods to Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Fields, Accessors, and Properties 147 How to Choose 151 Mutable Properties 153 Refactoring to Properties 153 Moving On 158 12. Functions to Operators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 A Foundational Class: Money 159 Adding a User-Defined Operator 161 Calling Our Operator from Existing Kotlin Code 163 Operators for Existing Java Classes 164 Conventions for Denoting Values 165 Moving On 168 13. Streams to Iterables to Sequences. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Java Streams 169 Kotlin Iterables 171 Kotlin Sequences 172 Multiple Iterations 173 Choosing Between Streams, Iterables, and Sequences 176 Algebraic Transformation 177 Refactoring from Streams to Iterables and Sequences 179 Moving On 191 14. Accumulating Objects to Transformations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Calculating with Accumulator Parameters 193 Refactoring to Functions over Immutable Data 198 Let’s Do That Again 203 Enriching the Abstraction We Discovered 206 Moving On 208 Table of Contents | vii
15. Encapsulated Collections to Type Aliases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Composing Domain Collections 211 Collections with Other Properties 212 Refactoring Encapsulated Collections 213 Moving On 222 16. Interfaces to Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Object-Oriented Encapsulation 226 Functional Encapsulation 229 Function Types in Java 232 Mix and Match 234 Comparing the Approaches 238 Coupling 241 Object-Oriented or Functional? 241 The Legacy of Java 245 Traceability 245 Refactoring from Interfaces to Functions 246 Moving On 252 17. Mocks to Maps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 Replacing Mocks with Maps 256 Have We Really Weaned Off Mocks, Though? 260 Moving On 263 18. Open to Sealed Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Polymorphism or Sealed Classes? 269 Converting an Interface to a Sealed Class 270 Moving On 276 19. Throwing to Returning. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Error Handling Before Exceptions 281 Error Handling with Exceptions 282 Java and Checked Exceptions 282 Kotlin and Exceptions 285 Beyond Exceptions: Functional Error Handling 285 Error Handling in Kotlin 288 Refactoring Exceptions to Errors 289 More Fixup 303 Layers 309 Moving On 311 viii | Table of Contents
20. Performing I/O to Passing Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 Listening to Tests 313 I/O to Data 315 Efficient Writing 320 Efficient Reading 324 Moving On 327 21. Exceptions to Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Identifying What Can Go Wrong 329 Representing Errors 336 What About I/O? 342 Moving On 344 22. Classes to Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 An Acceptance Test 346 Unit Testing 349 Headers 354 Different Field Separators 361 Sequences 367 Reading from a File 372 Comparison with Commons CSV 377 Moving On 381 23. Continuing the Journey. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 Grain 383 Functional Thinking 384 Simple Design 386 Functional Programming and Textual Reasoning 387 Refactoring 387 Refactoring and Functional Thinking 388 Bibliography. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Learning Kotlin 391 Testing and Test-Driven Development 391 Working with Legacy Code 391 Software Design and Development 392 Java and the JVM 392 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 Table of Contents | ix
(This page has no text content)
Preface Hello, this is Duncan and Nat. As you’re reading this preface, you’re probably trying to decide whether to invest some hours into reading the rest of this book. So let’s cut to the chase: This book won’t teach you to program computers in Kotlin. We started writing a book that would, but it soon became clear that Kotlin is a large language, and so the book was going to take longer to write than we wanted. There are also already some great books in that space, and we don’t like competing against great. We decided instead to make our lives easier by concentrating on teaching Kotlin to Java developers, based on a workshop that we run called Refactoring to Kotlin. This teaches the Kotlin language by converting existing code and is (according to our mar‐ keting material) designed for Java teams wanting to leverage their existing knowledge to accelerate their Kotlin adoption. We started writing that book, but it soon became clear that Kotlin is still a large lan‐ guage, and so we would still be writing for a long time. We also found that motivated and experienced Java developers can pick up most of Kotlin very quickly. It felt patronizing to plow our way through language features that our target readers will probably just appreciate and adopt as soon as they see them. So we abandoned that idea, and as a result: This book won’t teach you the Kotlin language. So why should you read it? Because we have written the book we wish was available when we first adopted Kotlin. We are experienced programmers who know Java and the Java ecosystem well. We hope you are too. Like us, you probably have experience in a number of other languages. You’ve learned the basics of Kotlin, and you recog‐ nize that to get the best out of the language you will need to design your systems dif‐ ferently. You have found that some things that are cumbersome in Java are much xi
easier in Kotlin, and that some features, such as checked exceptions, are not there at all. You don’t want to end up merely writing Java code in Kotlin syntax. Perhaps you have skin in the game. Maybe you’re in a technical leadership position, or have successfully convinced your team to adopt Kotlin. You might have spent some political capital to get Kotlin into the project. Now you need to ensure that the transi‐ tion goes smoothly. You may be responsible for a Java codebase and want to ensure that introducing Kot‐ lin won’t destabilize its existing, business-critical code. Or you may be starting a Kot‐ lin project from scratch but realize your design instincts turn more readily to Java and objects than to Kotlin and functions. If this is you, as it was us, then you’ve come to the right place. This book will help you adapt your thinking and designs to take advantage of Kotlin. That’s not enough, though, because you have existing code that you need to maintain and enhance. So we also show how to migrate that code from Java to Kotlin syntax, and from Java to Kotlin thinking, incrementally and safely, using the automated refactoring tools built into the IntelliJ IDE. How This Book Is Organized This book is about how to transition from Java to Kotlin, mainly focused on code but touching on projects and organizations. Each chapter addresses an aspect of this tran‐ sition, looking at some aspect of typical Java projects that can be improved on the journey. They are named in the pattern Java Way to Kotlin Way, where we recom‐ mend that you prefer the latter over the former. Maybe Kotlin makes easier an approach that was difficult in Java, or Kotlin discourages an approach that is common in Java to guide design in a direction that is less error-prone, more concise, and more tool-friendly. We don’t just recommend you adopt the Kotlin way though; the chapters also show how to make the transformation. Not by just rewriting the Java, but by gradually refactoring it to Kotlin it in a way that is safe and allows us to maintain a mixed lan‐ guage codebase. How Did We Choose the Topics? We began by analyzing Java and Kotlin developers’ use of their respective languages and conducting interviews to identify areas of difference and confusion. This was backed by a machine learning analysis of 33,459 open source Java and Kotlin codeba‐ ses. These identified candidates that we labeled in the thing-to-another-thing form before ranking them according to frequency and developer-pain-quotient in order to establish which should make the cut. Finally, we ordered the surviving topics by… xii | Preface
…it’s no good, we can’t lie to you. The truth is that we started by choosing topics that we wanted to write about, and that we felt would be interesting and informative. Chapter 15, Encapsulated Collec‐ tions to Type Aliases, Chapter 9, Multi- to Single-Expression Functions, and Chapter 20, Performing I/O to Passing Data are typical of these chapters. We also looked for places where the grain of Kotlin and Java differ significantly, because those were the places where we found we learned most by asking why they were different. This led to chap‐ ters like Chapter 4, Optional to Nullable, Chapter 6, Java to Kotlin Collections, and Chapter 8, Static Methods to Top-Level Functions. As we wrote those chapters, other topics presented themselves and were added to the list. In particular, as we wrote the refactoring steps for a chapter, we often found our‐ selves making changes to code that we felt deserved their own chapter. Chapter 13, Streams to Iterables to Sequences, Chapter 10, Functions to Extension Functions, and Chapter 11, Methods to Properties are examples of these. The result of this process is by no means exhaustive. If you have already skimmed the table of contents or index, you will find important topics unaddressed. Take co- routines for example: this paragraph is the only reference to this huge subject, because we have found that they haven’t changed the way that we write server-side code, so we didn’t want to write about them. There are also topics that we would like to have covered if only we had space and time, including: builders, domain-specific languages, reflection, dependency injection frameworks, transactions…the list goes on! We hope that what we have written about is interesting to you. It is largely a book of tactics rather than strategies, concentrating on the small battles that we can win from our place in the trenches, rather than what might be achieved by directing whole divi‐ sions. As larger themes emerge, we will try to connect them, though, and to bring things together in the final chapter, Chapter 23, Continuing the Journey, where we talk about what we have learned during the writing process. Complexity How should we judge the internal quality of our software? Assuming that it does what our customers want or need it to do, how can we compare two potential implementa‐ tions, or decide whether a change makes one better or worse? The answer that your authors choose is complexity. Other things being equal, we favor simple designs that yield predictable behavior. Of course to some extent, simplicity and complexity are in the eye of the beholder. Your authors do have slightly different personal preferences and so sometimes disa‐ gree over whether one implementation or another is better. Where that happens, we sometimes explore the alternatives in the relevant chapter. However, we do both have Preface | xiii
a shared belief in the power of functional programming to reduce the complexity of our systems, especially when combined with object-oriented (OO) message passing. Java has been moving in this direction over the years. Scala ran toward functional programming but away from OO. We find that the grain of Kotlin lets us mix func‐ tional and object programming in a way that reduces complexity and brings out the best in mere mortal developers. Perfect Code On the subject of mere mortals, we should address code quality. It is sorely tempting to aim for perfection when committing code to a book. We know that you will be judging us by the code here, and like many developers, a great deal of our own self- worth is tied up in the quality of the work we produce. At the same time, we are engineers not artists. Our job is to balance scope, schedule, and cost for our customers. No one but us really cares about the quality of the code except when it affects one of these three higher values. So in our examples, we have tried to show realistic production code. The starting points are sometimes not as good as we might like; we are, after all, trying to show ways of improving them. Often refactorings will make things worse before they get better, so definitely don’t judge us by code in the middle of a chapter. By the end of a chapter, our aim is to have code that is good enough, but not so perfect that we could be accused of wasting our clients’ money. That said, we have a policy of applying cost-effective changes to tidy up, even once we have covered the topic we set out to illustrate, and more than once we have invented a topic, and written a chapter just to leave the code in a state that we’re happy with. In the end, we are artists as well as engineers. Code Formatting Our code follows (our interpretation of) the standard coding conventions of Java and Kotlin where possible. The practical line length for printed code samples is much shorter than the 120 char‐ acters we usually use in an IDE these days, so we have had to split lines more often than usual to make the code fit in the page width. Our production code might have four or five parameters or arguments on a line; in this book we will often only have one. Through formatting the examples for the page, we have come to like the more vertical style. We find that Kotlin naturally seems to want to take more vertical space than Java, but even Java readability seems improved by shorter lines, more breaks, and more visual alignment. Certainly scrolling sideways is almost as inconvenient in an IDE as in a book, and our pairing sessions are improved by less scrolling and more xiv | Preface
side-by-side windows. One line per parameter also greatly improves diffs between code versions. We hope that at the very least you don’t find it too painful to read, and if you don’t, then try it for your own code. We will sometimes hide code that isn’t relevant to the discussion. A line that starts with an ellipsis of three dots indicates that we have omitted some code for clarity or brevity. For example: fun Money(amount: String, currency: Currency) = Money(BigDecimal(amount), currency) ... and other convenience overloads Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program ele‐ ments such as variable or function names, databases, data types, environment variables, statements, and keywords. This element signifies a tip or suggestion. This element signifies a general note. This element indicates a warning or caution. Preface | xv
Using Code Examples Most of the code examples in the book (the ones from the refactoring sections) can be accessed online on GitHub. The reference is immediately after the code, like this: class TableReaderAcceptanceTests { @Test fun test() { } } Example 0.1 [table-reader.1:src/test/java/travelator/tablereader/TableReaderAcceptanceTests.kt] If you are reading this on a device, the reference should be a hyperlink to that version of the file on GitHub. On real paper, you can click all you like; nothing will happen, sorry. But if you take the example number, in this case 0.1, and type it into a form on the book’s website, it will show you links that take you to the same place. In Git, the different code examples (these sometimes span multiple chapters) evolve in separate branches. The steps are tagged—table-reader.1 is the tag in this case. The GitHub link is to code with that tag, so you can view the file shown (src/test/java/trav‐ elator/tablereader/TableReaderAcceptanceTests.kt here) and the others in the example at that version. You can also select other tags to see the different versions, and differ‐ ent branches to see different examples. For quicker navigation, you can clone the repository, open it in IntelliJ, and use the Git tool window to switch branches and versions. The code examples are not real! The codebase builds and passes its tests, but it is fictional. There are places where the examples don’t join up properly, and others where if you peek behind the curtain you will see us wiggling the levers. We have tried to be honest, but prefer to ship! If you have a technical question or a problem using the code examples, visit the book’s website or email bookquestions@oreilly.com. This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. xvi | Preface
We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Java to Kotlin by Dun‐ can McGregor and Nat Pryce (O’Reilly). Copyright 2021 Duncan McGregor and Nat Pryce, 978-1-492-08227-9.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. O’Reilly Online Learning For more than 40 years, O’Reilly Media has provided technol‐ ogy and business training, knowledge, and insight to help companies succeed. Our unique network of experts and innovators share their knowledge and expertise through books, articles, conferences, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in- depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, please visit http://oreilly.com. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at https://oreil.ly/java-to-kotlin. Email bookquestions@oreilly.com to comment or ask technical questions about this book. For news and information about our books and courses, visit http://www.oreilly.com. Find us on Facebook: http://facebook.com/oreilly Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://youtube.com/oreillymedia Preface | xvii
Acknowledgments Thank you to Hadi Hariri for suggesting to O’Reilly that we should write a book, and to Zan McQuade for believing him. Thank you to our editor Sarah Grey, who had to live with the consequences, and Kerin Forsyth and Kate Galloway for tidying every‐ thing up and actually getting it published. Many friends and colleagues, and some lovely strangers, reviewed drafts ranging from early and uncoordinated to tantalizingly nearly complete. Thank you to Yana Afanasyeva, Jack Bolles, David Denton, Bruce Eckel, Dmitry Kandalov, Kevin Peel, James Richardson, Ivan Sanchez, Jordan Stewart, Robert Stoll, Christoph Sturm, Łukasz Wycisk, and Daniel Zappold and to our technical reviewers, Uberto Barbini, James Harmon, Mark Maynard, and Augusto Rodriguez. We really appreciate all your suggestions, encouragement, and candor. Extreme Programming revolutionized the way that write software—we all owe a debt of gratitude to Ward Cunningham and Kent Beck. Thank you also to Martin Fowler, without whom this book might not have been written. In the UK the eXtreme Tues‐ day Club has been innovating on these ideas since 1999, and has attracted a cabal of developers. We are lucky to have worked with, and learned from, many talented members of this group. If you have a problem, if no one else can help, and if you can find them, maybe you can hire them. Duncan’s Bit I don’t think my wife will ever understand what I do for a living, and there’s no chance that she will read the rest of this book, but she will probably get this far. So thank you, Jo McGregor, for putting up with me writing rather than spending time with you, and talking about writing when I was spending time with you. I couldn’t have done it without your support and encouragement. Thank you also to our two wonderful sons, Callum and Alistair, who make us so proud. Thank you to Vickie Kennish for taking a keen interest in becoming the mother of an author, checking on progress during our COVID lockdown walks. My late father, John, would I’m sure have played it more casual, but be bragging about the book to his friends. Also gone but not forgotten is our beautiful cat Sweet Pea, who kept me company through most of the writing but died just before it was finished. The friendship and support of Robin Helliwell has been a constant throughout my adult life. Likewise, my sister Lucy Seal, and many other family members too numer‐ ous to list individually. In my professional life, in addition to those who gave feedback, thank you to Alan Dyke, Richard Care, and Gareth Sylvester-Bradley, all influential and supportive beyond the call of duty. xviii | Preface
Comments 0
Loading comments...
Reply to Comment
Edit Comment