7 consejos sobre cómo hacer que tu biblioteca Kotlin brille

Prefacio

Cuando comenzamos a programar en un nuevo lenguaje, al principio a menudo nos atenemos a los paradigmas y hábitos desarrollados mientras programamos en el lenguaje que ya conocemos. Aunque al principio parezca estar bien, para las personas que programan en el idioma que están tratando de dominar por un tiempo, esta aspereza es bastante obvia.

Cuando me había cambiado de Java a Kotlin hace unos 2 años, básicamente estaba haciendo "programación Java pero en Kotlin". A pesar de que Kotlin era como un soplo de aire fresco para mí, como ingeniero de Android estaba usando todas esas funciones de extensión y clases de datos y no siempre era coherente. Estaba convirtiendo el código Java existente a Kotlin y mirarlo después de algún tiempo me ayudó a tener algunas ideas en mente cuando diseñé API para diferentes componentes o bibliotecas reutilizables en Kotlin.

Aquí en BUX, estamos trabajando en dos aplicaciones que tienen un par de bibliotecas compartidas. Si bien la aplicación Stocks (que aún no se lanzó) se escribió en Kotlin desde el principio, la primera aplicación CFD se escribió en Java y luego se convirtió a Kotlin después de un tiempo. En este momento, la aplicación CFD es 78.7% Kotlin, lo que significa que todavía estamos trabajando en ello y es por eso que es importante prestar atención a las API de biblioteca mantenidas por dos equipos que usan Java y Kotlin al mismo tiempo.

Entonces, si tiene una biblioteca Kotlin o Java existente que desea Kotlinify o está diseñando una API usando Kotlin desde cero, tenga en cuenta algunas ideas de lo que puede hacer para facilitar la vida de los usuarios de su biblioteca.

1. Funciones de extensión

La mayoría de las veces, cuando necesita extender una clase existente con una nueva funcionalidad, usa algún tipo de composición o deriva una nueva clase (que, si se usa ampliamente, puede hacer que su jerarquía de clases sea tan frágil como los vasos de vidrio de IKEA). No importa cuál sea su preferencia, Kotlin tiene su propia respuesta para esto, llamada funciones de extensión. En pocas palabras, le permite agregar una nueva función a una clase existente con una sintaxis bastante clara. En Android, por ejemplo, puede definir un nuevo método para la clase View de la siguiente manera:

Teniendo esto en cuenta, muchas bibliotecas que proporcionan funcionalidad adicional para las clases existentes (por ejemplo, Vista, Contexto, etc.), en lugar de usar decoradores, métodos de fábrica estáticos o algo diferente pueden beneficiarse al proporcionar su funcionalidad como funciones de Extensión sin problemas, como si esto La funcionalidad existiría en las clases originales desde el principio.

2. Valores de argumento predeterminados

Originalmente, Kotlin fue diseñado para ser una versión concisa, ordenada y ordenada de Java y resulta que lo hace muy bien con valores de argumento de función predeterminados. El proveedor de la API puede especificar valores predeterminados para los argumentos que podrían omitirse. Me sorprendería mucho si no has visto un código similar mientras trabajas con SQLite en Android:

Aunque hay más fallas en este método que esos nulos feos al final, no estoy aquí para juzgar sino para decir que estos tiempos se han ido y cuestionaría seriamente el código escrito de esta manera en Kotlin. Hay muchos ejemplos como este, pero, afortunadamente, podemos hacerlo mejor ahora y si proporciona una API externa con ciertos parámetros de ajuste, en lugar de, o además de proporcionar un Generador, considere usar valores de argumento predeterminados. Esto tendrá al menos dos ventajas: en primer lugar, no obligará a un usuario a especificar parámetros opcionales o lo que sea que pueda predecir de antemano y el segundo es que el usuario tendrá una configuración predeterminada que simplemente funcionará -caja:

Además, una buena cosa para mencionar aquí es que si coloca esos argumentos con valores predeterminados al final, un usuario no tendrá que especificar nombres para los parámetros obligatorios.

3. Objetos

¿Alguna vez ha tenido que implementar el patrón Singleton en Java usted mismo? Probablemente lo haya hecho y, de ser así, debe saber lo engorroso que puede ser a veces:

Existen diferentes implementaciones, cada una con sus propios pros y contras. Kotlin aborda todos esos 50 tonos de implementación del patrón Singleton con una estructura única llamada declaración de objeto. Mira ma, no hay "bloqueo doblemente verificado":

Lo bueno de esto, aparte de la sintaxis, es que la inicialización de la declaración de objeto es segura para subprocesos e inicializada de forma perezosa cuando se accede por primera vez:

Para bibliotecas como Fabric, Glide, Picasso o cualquier otra que use una sola instancia del objeto como el punto de entrada principal a la API general de la biblioteca, es una forma natural de hacerlo ahora y no hay razón para usar la forma antigua de Java para haz las mismas cosas

4. Archivos de origen

De la misma forma en que Kotlin reconsidera una gran cantidad de desafíos sintácticos que enfrentamos a diario, también replantea la forma en que organizamos el código que creamos. Los archivos fuente de Kotlin sirven como hogar para múltiples declaraciones que están semánticamente cercanas entre sí. Resulta que son el lugar perfecto para definir algunas funciones de extensión relacionadas con la misma clase. Consulte este fragmento simplificado del código fuente de Kotlin, donde todas las funciones de extensión utilizadas para manipular el texto se encuentran en el mismo archivo "Strings.kt":

Otro ejemplo adecuado de su uso es definir un protocolo de comunicación con su API junto con las clases de datos y las interfaces en un solo archivo fuente. Permitirá que un usuario no pierda el foco al cambiar entre diferentes archivos mientras sigue un flujo:

Pero no vaya demasiado lejos, ya que corre el riesgo de abrumar a un usuario con el tamaño del archivo y convertirlo en una clase RecyclerView con ~ 13,000 líneas .

5. Corutinas

Si su biblioteca utiliza múltiples hilos para acceder a una red o realizar cualquier otro trabajo de larga duración, considere proporcionar una API con funciones de suspensión. A partir de Kotlin 1.3, las corutinas ya no son experimentales, lo que es una gran oportunidad para comenzar a usarlas en producción si ha tenido dudas antes. Las rutinas en algunos casos pueden ser una buena alternativa a Observable de RxJava y otras formas de tratar con llamadas asincrónicas. Lo que quizás ya haya visto antes es la API llena de métodos con devoluciones de llamada de resultados o en el momento de Rx hype envuelto con Single o Completable:

Pero ahora estamos en la era de Kotlin, por lo que también podría diseñarse a favor del uso de corutinas:

A pesar de que las corutinas funcionan mejor que los viejos hilos de Java en términos de ser más livianas, contribuyen significativamente a la legibilidad del código:

6. Contratos

Junto con las rutinas estables, Kotlin 1.3 también presenta a los desarrolladores la forma de interactuar con un compilador. Un contrato es una nueva característica que nos permite, como desarrolladores de bibliotecas, compartir con un compilador el conocimiento que tenemos al especificar los llamados efectos. Para que sea más fácil entender cuál es el efecto, traigamos su analogía más cercana de Java. La mayoría de ustedes probablemente han visto o incluso usado la clase Precondition de Guava con muchas afirmaciones y su adaptación dentro de bibliotecas como Dagger que no quieren obtener una biblioteca completa por completo:

Si el objeto de referencia pasado es nulo, el método arrojará una excepción y no se alcanzará el código restante colocado después de este método, por lo que podemos suponer con seguridad que la referencia no será nula allí. Aquí viene el problema: aunque tenemos este conocimiento, no ayuda al compilador a hacer la misma suposición. Aquí es donde las anotaciones @ Nullable y @ NotNull entran en escena, ya que solo existen para ayudar al compilador a comprender las condiciones. Esto es lo que realmente es un efecto: es una pista para un compilador que lo ayuda a realizar un análisis de código más sofisticado. Un efecto existente que ya has visto en Kotlin es el lanzamiento inteligente de un cierto tipo en el bloque después de verificar su tipo:

El compilador de Kotlin ya es inteligente y sin dudas seguirá mejorando en el futuro, pero con la introducción de los contratos, los desarrolladores de Kotlin nos dieron como desarrolladores de la biblioteca el poder de mejorarlo escribiendo nuestros propios modelos inteligentes y creando efectos que ayuden a nuestros usuarios a escribir código más limpio Ahora, reescribamos el método checkNotNul en Kotlin usando Contratos para demostrar sus capacidades:

También hay diferentes efectos, pero esta no es una introducción completa a los Contratos y espero que les haya dado una idea de cómo pueden beneficiarse de ellos. Además de eso, el repositorio stdlib en Github tiene muchos ejemplos de contratos útiles que vale la pena consultar.

Con un gran poder viene una gran responsabilidad y los contratos no son una excepción. Cualquier cosa que declare en su Contrato es tratada por el compilador como la Santa Biblia. Más técnicamente, el compilador no cuestiona ni valida todo lo que escribe allí, por lo que debe inspeccionar cuidadosamente su código usted mismo y asegurarse de no introducir ninguna inconsistencia.

Como habrás notado en la anotación @ExperimentalContracts, los contratos todavía están en la fase experimental, por lo que no solo la API puede cambiar con el tiempo, sino que también pueden derivar algunas características nuevas a medida que se vuelve más madura.

7. interoperabilidad Java

Lo que también es importante al escribir una biblioteca en Kotlin es mantener a la audiencia más amplia al proporcionar una experiencia de integración fluida para sus colegas desarrolladores que usan Java. Es muy importante ya que todavía hay muchos proyectos que se adhieren a Java y no quieren reescribir el código existente por varias razones. Dado que queremos que la comunidad de Kotlin crezca más rápido, es mejor permitir que esos proyectos lo hagan gradualmente, paso a paso. Por supuesto, no hace tanto sol en este lado de The Wall, pero hay algunas cosas que usted como administrador de la biblioteca puede hacer al respecto.

Lo primero es que las funciones de extensión de Kotlin en Java se compilarán en métodos estáticos feos donde el primer argumento del método es un tipo de receptor:

Si bien no tiene mucho control aquí, al menos puede cambiar el nombre de la clase generada agregando la siguiente línea al comienzo del archivo fuente que contiene las funciones de nivel de paquete anteriores:

Otro ejemplo de cómo puede impactar levemente un código generado está relacionado con el uso de métodos de objetos complementarios:

Puede obligar a Kotlin a generar clases estáticas en lugar de las funciones definidas en el objeto complementario utilizando la anotación @JvmStatic:

En comparación con Kotlin, en Java dos funciones con nombres similares pero diferentes tipos genéricos no se pueden definir juntas debido a la eliminación de tipos, por lo que podría ser un obstáculo para los usuarios de Java. Con suerte, puede evitarlo cambiando la firma del método con otra anotación:

Y por último, pero no menos importante, está la capacidad de definir excepciones marcadas en las funciones para los usuarios de Java, aunque no están directamente disponibles en Kotlin. Esto podría ser útil ya que el paradigma de la declaración de excepción en Java y Kotlin es diferente:

La mayoría de las cosas aquí son opcionales, pero ciertamente pueden ser una fuente de felicitaciones para su biblioteca por parte de los usuarios de Java.

Línea de fondo

Kotlin es un gran lenguaje que avanza constantemente y hay muchas cosas que puede hacer sobre la biblioteca para que su uso sea más fluido. Entre todo lo que aplique de las ideas anteriores, intente ser un usuario en primer lugar y piense qué le gustaría que fuera y cómo lo usaría. El Diablo está en los detalles y, con suerte, estas pequeñas cosas que aplique beneficiarán a sus usuarios y mejorarán su experiencia. ¡Aclamaciones!