Esta es una colección cínica y clínica de lo que aprendí durante 30 años en el desarrollo de software. Repito, algunas cosas son muy cínicas, y el resto es el resultado de largas observaciones en diferentes lugares de trabajo.
Desarrollo de software
Especificaciones primero, luego código
Si no sabe exactamente qué está tratando de resolver, entonces no sabe qué código escribir.
Primero, describa cómo funciona su aplicación antes de comenzar a programar.
"Sin requisitos ni un proyecto, la programación es el arte de agregar errores a un archivo de texto vacío". - Louis SraigleyA veces, incluso una "breve presentación" es suficiente, no más de dos párrafos que describen lo que hace su aplicación.
Ha habido momentos en que, debido a pasos no escritos, pasé más tiempo mirando el código y preguntándome qué hacer a continuación. Esta es una buena señal de que es hora de detenerse y discutir la situación con sus colegas. O tal vez reconsiderar la decisión.
Describa los pasos como comentarios.
Si no sabe cómo comenzar, describa el flujo de datos de nivel superior en su aplicación, simplemente en su idioma nativo. Y luego complete el código vacío entre los comentarios.
O incluso mejor: lea cada comentario como una función, y luego escriba una función que haga exactamente eso.
Gherkin te ayuda a cumplir tus expectativas
Gherkin es un formato de descripción de prueba cuyo principio dice: "Dado que el sistema se encuentra en un cierto estado, si algo sucede, entonces se espera". Si no utiliza herramientas de prueba que entiendan a Gherkin, le dará una buena idea de qué esperar de la aplicación.
Las pruebas unitarias son buenas, las pruebas de integración son aún mejores
En mi trabajo actual, estamos probando solo módulos y clases. Por ejemplo, escribimos pruebas solo para el nivel de presentación, luego escribimos pruebas solo para el nivel del controlador, y así sucesivamente. Esto nos ayuda a comprender si todo está en orden, pero no nos permite ver la imagen completa de lo que está sucediendo; para esto, las pruebas de integración que verifican el comportamiento de todo el sistema son mucho más útiles.
Las pruebas mejoran las API
Programamos dentro del marco de niveles: hay un nivel de almacenamiento que debería hacer que nuestros datos sean eternos; hay un nivel de procesamiento que de alguna manera debe transformar los datos almacenados; hay una capa de presentación que contiene información sobre la presentación de datos, etc.
Como dije, las pruebas de integración son mejores, pero probar los niveles mismos le permite comprender mejor cómo se ven sus API. Entonces comprenderá mejor la situación con las llamadas de algo: ¿es la API demasiado complicada? ¿Necesito tener tantos datos cerca para hacer una llamada?
Haga pruebas que pueda ejecutar desde la línea de comando
Quiero decir, no las líneas de comando en sí mismas son importantes para cualquier objeto, sino su conocimiento de los comandos para ejecutar las pruebas, su capacidad para automatizar su ejecución, que luego puede aplicar en la herramienta de integración continua.
Prepárate para enviar tu código a la cesta
Muchos de los que comienzan a desarrollar sobre la base de las pruebas (TDD) se molestan cuando les dice que es posible que deba volver a escribir gran parte de su código, incluido lo que usted mismo escribió.
TDD fue
inventado para lanzar código: cuanto más aprendes sobre un problema, más entiendes que lo que escribes no lo resolverá a largo plazo.
No te preocupes por esto. Su código no es un muro: si siempre tiene que tirarlo, esto no es un desperdicio. Por supuesto, ha perdido tiempo escribiendo código, pero ahora comprende mejor el problema.
Un buen lenguaje tiene pruebas integradas
Le aseguro que si la biblioteca de idiomas estándar tiene un marco de prueba, aunque sea mínimo, en el ecosistema relacionado las pruebas serán mejores que en un lenguaje que no tenga dicho marco, independientemente de los méritos de los marcos de prueba externos para este idioma.
Pensar en el futuro significa malgastar tu energía
Cuando los desarrolladores intentan resolver un problema, a veces intentan encontrar una manera de resolver todos los problemas, incluidos los que puedan surgir en el futuro.
Te diré una cosa: estos problemas futuros nunca surgirán, y tendrás que acompañar una gran cantidad de código que no se usará en su totalidad, o tendrás que volver a escribir todo debido al montón de código no utilizado.
Resuelve el problema ahora. Luego decide lo siguiente. Luego el siguiente. Una vez que note un patrón que surge sobre la base de estas decisiones, y solo
entonces encontrará su "solución universal".
La documentación es un mensaje de amor futuro para ti
Todos sabemos qué tipo de hemorragia es escribir maldita documentación sobre funciones, clases y módulos. Pero comprender el curso de sus pensamientos cuando escribió esta o aquella función puede salvar su trasero en el futuro.
La documentación de la función es su contrato.
Al comenzar a escribir documentación, en realidad está creando un contrato (tal vez con usted mismo): "Afirmo que esta función hace
esto , y eso es lo que hace".
Si luego descubre que su código no cumple con la documentación, entonces esto será un problema de código, no una documentación.
Si la descripción de la función tiene "y", entonces esto es malo
Una función solo debe hacer una cosa. Cuando le escribes documentación y ves que has agregado una "y", significa que la función hace algo más. Dividirlo en dos funciones y deshacerse de la "y".
No utilice valores booleanos como parámetros.
Al desarrollar una función, puede sentirse tentado a agregar una bandera. No hagas esto.
Permítame explicarlo con un ejemplo: supongamos que tiene un sistema de mensajería y hay una función
getUserMessages
que devuelve todos los mensajes al usuario. Pero hay una situación en la que debe devolver un breve resumen de cada mensaje (por ejemplo, el primer párrafo) o el mensaje completo. Por lo tanto, agrega un parámetro en forma de una marca o un valor booleano al que llama
retrieveFullMessage
.
De nuevo, no hagas esto.
Porque aquellos que leen su código verán
getUserMessage(userId, true)
y se preguntarán de qué se trata.
O puede cambiar el nombre de la función
getUserMessageSummaries
e ingresar
getUserMessagesFull
, o algo similar, pero cada función simplemente llamará al
getUserMessage
original con
true
o
false
, pero la interfaz fuera de su clase / módulo será clara.
Pero no agregue banderas ni parámetros booleanos a las funciones.
Cuidado con los cambios de interfaz
En el párrafo anterior, mencioné el cambio de nombre de una función. Si controla la fuente en la que se utiliza la función, entonces esto no es un problema, es solo una cuestión de búsqueda y reemplazo. Pero si la biblioteca proporciona la función, no necesita cambiar el nombre por su propia cuenta. Esto romperá muchas otras aplicaciones que no controlas y molestará a muchas personas.
Puede crear nuevas funciones y marcar la función actual como indeseable en un documento o mediante código. Y después de unos pocos lanzamientos, finalmente puedes matarla.
Solución fea: cree nuevas funciones, marque la actual como indeseable y
agregue la sleep
al comienzo de la función para obligar a aquellos que usan la función anterior a actualizarse.
Los buenos idiomas tienen documentación incorporada
Si el lenguaje usa su propia forma de documentar funciones, clases, módulos y todo lo demás, e incluso hay un generador de documentación simple, entonces todo lo mencionado estará bien documentado (no es bueno, pero al menos es bueno).
Y los idiomas que no tienen documentación incorporada a menudo están mal documentados.
El lenguaje es más que solo lenguaje
Escribes en un lenguaje de programación y haces que las cosas "funcionen". Pero no hay solo palabras especiales: el lenguaje tiene un sistema de ensamblaje, un sistema de gestión de dependencias, herramientas para interactuar, bibliotecas y marcos, hay una comunidad, hay una manera de interactuar con las personas.
No seleccione idiomas para facilitar su uso. Recuerde que puede encontrar la sintaxis simple, pero al elegir este idioma, también elige la forma en que los creadores del idioma se comunican con su comunidad.
A veces es mejor dejar que la aplicación se bloquee que no hacer nada.
Aunque esto suena extraño, es mejor no agregar manejo de errores que detectarlos en silencio y no hacer nada.
Java tiene un patrón tristemente común:
try { something_that_can_raise_exception() } catch (Exception ex) { System.out.println(ex); }
Aquí no se hace nada con excepción, solo se muestra un mensaje.
Si no sabe cómo manejar el error, deje que ocurra, para que al menos pueda saber
cuándo sucedió.
Si sabes cómo procesar, hazlo
A diferencia del párrafo anterior: si sabe cuándo aparece una excepción, un error o un resultado, y sabe cómo manejarlo, hágalo. Muestre el mensaje de error, intente guardar los datos en algún lugar, descarte los datos ingresados por el usuario para su uso posterior en el registro, solo
procese .
Los tipos hablan sobre qué datos tienes
La memoria es solo una secuencia de bytes. Los bytes son simplemente números del 0 al 255. El significado de estos números se describe en el sistema de tipo de idioma.
Por ejemplo, en C, el tipo de carácter (tipo char) con un valor de 65 probablemente será la letra "A", y un int con un valor de 65 será el número 65.
Tenga esto en cuenta cuando trabaje con sus datos.
Al agregar booleanos, muchos olvidan verificar el número de valores
True
. Recientemente me encontré con este ejemplo de JavaScript:
console.log(true+true === 2); > true console.log(true === 1); > false
Si sus datos tienen un esquema, almacénelo como una estructura
Si los datos son simples, por ejemplo, solo dos campos, puede almacenarlos en una lista (o una tupla si su idioma lo permite). Pero si los datos tienen un esquema, un formato fijo, siempre use alguna estructura o clase para almacenarlos.
Reconocer y mantenerse alejado del culto a la carga.
La idea del "culto a la carga" es que si alguien lo hizo, entonces podemos hacerlo. Muy a menudo, el culto a la carga es simplemente un "escape fácil" del problema: ¿por qué deberíamos pensar en cómo almacenar adecuadamente los datos del usuario si X ya lo ha hecho?
"Si la Gran Compañía almacena datos de esta manera, entonces podemos".
"Si Big Company lo usa, entonces está bien".
"La herramienta adecuada para la tarea" es una manera de imponer su opinión
La frase "la herramienta adecuada para la tarea" debería significar que hay una herramienta correcta e incorrecta para algo. Por ejemplo, usando un lenguaje o marco específico en lugar del lenguaje o marco actual.
Pero cada vez que escucho esta expresión de alguien, la gente empuja su idioma / marco favorito de esta manera, en lugar de, digamos, el idioma / marco correcto.
La "herramienta correcta" es más obvia de lo que piensas
Quizás ahora esté participando en un proyecto en el que desea procesar algún texto. Es posible que desee decir: "Usemos Perl porque todos saben que Perl es muy bueno procesando texto".
Lo que olvida: su equipo se especializa en C. Todos conocen C, no Perl.
Por supuesto, si este es un pequeño proyecto "en la rodilla", entonces es posible en Perl. Y si el proyecto es importante para la empresa, es mejor escribirlo en C.
PD: Su proyecto heroico (más sobre esto a continuación) puede fallar debido a esto.
No encajes en lo que está fuera de tu proyecto
A veces, en lugar de utilizar herramientas de extensión apropiadas, las personas comienzan a cambiar bibliotecas y marcos externos. Por ejemplo, realice cambios directamente en WordPress o Django.
Por lo tanto, puede hacer que el proyecto sea inadecuado y rápido para el mantenimiento. Tan pronto como se lance la nueva versión, tendrá que sincronizar los cambios con el proyecto principal, y pronto descubrirá que ya no puede aplicar los cambios y dejará la versión anterior de la herramienta externa llena de agujeros de seguridad.
Los flujos de datos superan los patrones
Esta es mi opinión personal. Si comprende cómo deben pasar los datos a través de su código, entonces será mejor para él que si usa un montón de patrones de diseño.
Los patrones de diseño se utilizan para describir soluciones, no para encontrarlas.
De nuevo mi opinión personal. Según mis observaciones, la mayoría de las veces los patrones de diseño se utilizan para encontrar una solución. Y como resultado, la solución, y a veces el problema en sí, se distorsiona para ajustarse al patrón.
Primero, resuelve tu problema. Encuentre una buena solución y luego busque entre los patrones para saber cómo se llama su solución.
Lo he visto muchas veces: tenemos un problema, el patrón está cerca de la solución correcta, usemos el patrón, ahora necesitamos agregar un montón de todo a la solución correcta, para que coincida con el patrón.
Aprenda los conceptos básicos de la programación funcional.
No es necesario profundizar en las preguntas "¿Qué son las mónadas?" O "¿Es un ficticio?". Pero recuerde: no debe cambiar constantemente los datos; cree nuevos elementos con nuevos valores (considere los datos como inmutables); en la medida de lo posible, realice funciones y clases que no almacenen estados internos (funciones y clases puras).
El esfuerzo cognitivo es el enemigo de la legibilidad.
La “
disonancia cognitiva ” es una expresión velada “para comprender esto, necesito recordar simultáneamente dos (o más) cosas diferentes”. Y cuanto más indirecta tenga esta información, más esfuerzo tendrá que dedicar a mantenerla en su cabeza.
Por ejemplo, agregar booleanos para contar valores
True
es una versión leve de disonancia cognitiva. Si lee el código y ve la función
sum()
, que, como sabe, agrega todos los números en la lista, entonces espera ver una lista de números; y conocí a personas que usan
sum()
para contar valores
True
en una lista de booleanos, lo cual es completamente confuso.
Número mágico siete más o menos dos
El "
número mágico " es un artículo en psicología que describe la cantidad de elementos que una persona puede mantener simultáneamente en la memoria a corto plazo.
Si tiene una función que llama a una función que llama a una función que llama a una función que llama a una función que llama a una función, entonces esto es simplemente un infierno para el lector de su código.
Piénselo bien: obtendré el resultado de esta función, la pasaré a la segunda función, obtendré su resultado, pasaré la tercera, etc.
Además, hoy en día los psicólogos hablan más a menudo del número mágico CUATRO, en lugar de siete.
Piense en la categoría de "composición de funciones" (por ejemplo, "llamaré a esta función, luego eso, luego allí ..."), y no en la categoría de "llamada de funciones" (por ejemplo, "esta función llamará a eso, llamará a eso. .. ").
Los recortes son buenos, pero solo a corto plazo
Muchos lenguajes, bibliotecas y marcos ofrecen métodos de acceso directo para reducir la cantidad de caracteres que escribe.
Pero más tarde vuelve a usted, y se verá obligado a eliminar los cortes y escribir todo en su totalidad.
Entonces, primero descubra qué está haciendo una abreviatura en particular antes de usarla.
No necesita escribir todo al principio, y luego cambiarlo a abreviatura: haga lo que las abreviaturas hacen por usted, y al menos comprenderá qué podría salir mal, o cómo reemplazar algo con una versión íntegra.
Resiste la tentación de la "facilidad"
Por supuesto, el IDE lo ayudará con la finalización automática de un montón de todo y facilitará la creación de un proyecto, pero ¿comprende lo que está sucediendo allí?
¿Sabes cómo funciona tu sistema de compilación? Si tiene que ejecutarlo sin un IDE, ¿puede hacerlo?
¿Recuerdas los nombres de funciones sin finalización automática? ¿Es posible romper algo o cambiarle el nombre para que sea más fácil de entender?
Interesarse en lo que sucede debajo del capó.
SIEMPRE use zonas horarias en fechas
Cuando trabaje con fechas,
siempre agregue zonas horarias.
Siempre tendrá problemas con las zonas horarias que no coinciden en las computadoras y los servidores, y perderá mucho tiempo para la depuración, tratando de entender por qué la interfaz muestra la hora incorrecta.
SIEMPRE use UTF-8
Tendrá los mismos problemas con las codificaciones que con las fechas. Por lo tanto, siempre convierta los valores de cadena a UTF-8, almacénelos en bases de datos en UTF-8 y regrese de sus API a UTF-8.
Puede convertir a cualquier otra codificación, pero UTF-8 derrotó a la guerra de codificación, por lo que es la forma más fácil de mantenerla.
Empieza estupido
Una de las formas de alejarse del IDE es "comenzar de una manera estúpida": simplemente tome el compilador, CUALQUIER editor con resaltado de código y - programe, compile, ejecute.
Si, no es facil. Pero cuando más tarde uses algún tipo de IDE, solo pensarás en los botones "Sí, lanza eso". Esto es exactamente lo que hacen los IDE.
Los registros son para eventos, no para la interfaz de usuario.
Durante mucho tiempo utilicé registros para mostrar a los usuarios lo que está sucediendo con la aplicación. Bueno, ya sabes, porque es mucho más fácil usar una cosa que dos.
Para informar a los usuarios sobre eventos, use el formulario de salida estándar. Para informes de errores, mensajes de error estándar. Y use los registros solo para almacenar datos que pueda procesar fácilmente más adelante.
Los registros no son una interfaz de usuario, sino una entidad que debe analizar para recuperar información en el momento adecuado. Los registros no deben ser legibles por humanos.
Depuradores sobrevalorados
He escuchado quejas de muchos de que los editores de código sin depuradores son terribles, precisamente porque no tienen depuradores.
Pero cuando su código está en funcionamiento, no
puede ejecutar su depurador favorito. Demonios, ni siquiera puedes ejecutar tu IDE favorito. Pero escribir un diario ... funciona en todas partes. Es posible que no tenga la información deseada en el momento de la caída (por ejemplo, debido a diferentes niveles de registro), pero
puede activar el registro para averiguar el motivo más adelante.
No guardo silencio sobre el hecho de que los depuradores son malos, simplemente no brindan la ayuda que muchos esperan de ellos.
Utilice siempre un sistema de versiones
"Esta es solo mi estúpida aplicación con la que quiero aprender algo" - esto no justifica la falta de un sistema de versiones.
Si utiliza un sistema de este tipo desde el principio, será más fácil retroceder cuando cometa un error.
Un cambio por confirmación
Conocí a personas que escriben los siguientes mensajes en commits: "Soluciona el problema 1, 2 y 3". A menos que todos estos problemas se dupliquen, de los cuales dos ya deberían estar cerrados, debería haber tres commits en lugar de uno.
Adherirse al principio de "un cambio por compromiso". Y por cambio quiero decir un cambio en un archivo. Si necesita cambiar tres archivos, confirme estos archivos juntos. Pregúntese: "si revierto este cambio, ¿qué debería desaparecer?"
"Git add -p" te ayudará con muchos cambios
Esto se aplica solo a Git. Le permite fusionar parcialmente archivos usando el parámetro "-p", de modo que solo puede seleccionar cambios relacionados entre sí, dejando a los demás para la nueva confirmación.
Estructurar proyectos por datos o tipo, no por funcionalidad
La mayoría de los proyectos usan la siguiente estructura:
. +-- IncomingModels | +-- DataTypeInterface | +-- DataType1 | +-- DataType2 | +-- DataType3 +-- Filters | +-- FilterInterface | +-- FilterValidDataType2 +-- Processors | +-- ProcessorInterface | +-- ConvertDataType1ToDto1 | +-- ConvertDataType2ToDto2 +-- OutgoingModels +-- DtoInterface +-- Dto1 +-- Dto2
Es decir, los datos están estructurados por funcionalidad (todos los modelos de entrada están en un directorio o paquete, todos los filtros están en otro directorio o paquete, etc.).
Funciona muy bien Pero cuando se estructura de acuerdo con los datos, es mucho más fácil dividir el proyecto en otros más pequeños, porque en algún momento es posible que deba hacer casi todo lo mismo que ahora, con solo pequeñas diferencias.
. +-- Base | +-- IncomingModels | | +-- DataTypeInterface | +-- Filters | | +-- FilterInterface | +-- Processors | | +-- ProcessorInterface | +-- OutgoingModels | +-- DtoInterface +-- Data1 | +-- IncomingModels | | +-- DataType1 | +-- Processors | | +-- ConvertDataType1ToDto1 | +-- OutgoingModels | +-- Dto1 ...
Ahora puede crear un módulo que funcione
solo con Data1, otro módulo que funcione solo con Data2, etc. Y luego puede separarlos en módulos aislados.
Y cuando necesite crear otro proyecto, que también contenga Data1 y trabaje con Data3, puede reutilizar la mayor parte del código en el módulo Data1.
Hacer bibliotecas
A menudo vi cómo los desarrolladores crean mega repositorios con diferentes proyectos o mantienen diferentes ramas, no para que sean un entorno temporal para unirse más tarde a la parte principal, sino simplemente para dividir el proyecto en partes más pequeñas (hablando de dividirse en módulos, imagine que en lugar de construir un nuevo proyecto que reutilice el tipo Data1, uso una rama con una función principal completamente diferente y el tipo Data3).
¿Por qué no asignar partes de uso frecuente a bibliotecas que se pueden conectar en diferentes proyectos?
La mayoría de las veces, la razón es que las personas no saben cómo crear bibliotecas, o les preocupa cómo "publicar" estas bibliotecas en fuentes de dependencia sin regalarlas (por lo tanto, ¿no es mejor entender cómo su herramienta de gestión de proyectos obtiene dependencias? para que pueda crear su propio repositorio de dependencias?).
Aprende a monitorear
En una vida anterior, agregué muchas métricas para comprender cómo se comporta el sistema: qué tan rápido llegó, qué tan rápido fue, cuánto hubo entre la entrada y la salida, cuántas tareas se procesaron ...
Esto realmente da una buena idea del comportamiento del sistema. ¿Está disminuyendo la velocidad? Para entenderlo, puedo verificar qué datos ingresan al sistema. ¿Es normal la reducción de velocidad en algún momento?
El hecho es que sin un monitoreo adicional, es bastante extraño tratar de descubrir cuán "saludable" es el sistema. Ya no es adecuado un control de salud al estilo de "Responde a consultas".
Agregar monitoreo temprano lo ayudará a comprender cómo se comporta el sistema.
Usar archivos de configuración
Imagínese: escribió una función en la que necesita pasar un valor para que comience a procesarse (por ejemplo, el ID de la cuenta en Twitter). Pero luego debe hacer esto con dos valores, y simplemente llama a la función nuevamente con un valor diferente.
Es mejor usar archivos de configuración y simplemente ejecutar la aplicación dos veces, con dos configuraciones diferentes.
Las opciones de la línea de comando se ven raras, pero son útiles
Si transfiere algo a los archivos de configuración, puede hacer la vida más fácil para sus usuarios y agregar la capacidad de seleccionar y abrir el archivo.
Hoy, para cada idioma, hay bibliotecas que funcionan con opciones para la línea de comandos. Le ayudarán a crear una buena utilidad al proporcionar una interfaz de usuario estándar para todo.
No solo composiciones de funciones, sino composiciones de aplicación
Unix utiliza este concepto: "aplicaciones que hacen una cosa y lo hacen bien".
Dije que puede usar una aplicación con dos archivos de configuración. ¿Y si necesita resultados de ambas aplicaciones? Luego puede escribir una aplicación que lea los resultados del segundo y combine todo en un resultado común.
Incluso cuando use la composición de la aplicación, comience estúpido
La composición de las aplicaciones puede convertirse en microservicios (lo cual es bueno), pero requieren una comprensión de cómo las aplicaciones se "comunican" entre sí a través de la red (protocolos y similares).
No es necesario comenzar desde esto. Las aplicaciones pueden escribir y leer archivos, mucho más fácil.
Pensará en la interacción remota más adelante cuando comprenda la red.Deja optimizaciones para compiladores
, . «, », , « , ».
, . , .
,
, , . , . . . , .
, , . Lisp, . , Python
yield
, , , . , , , , .
, , . , « », « » ..
, .
,
, .
, , «»: , , , . , , . , , , .
, , . , .
, . (« ?»), .
… Google
, . , Google , . , , Google , .
C/C++ — K&R
. :)
Python — PEP8
PEP8. , .
, ?
sleep()
.
? ?
, .
sleepForSecs
sleepForMs
, ,
sleep
.
, .
«Zen of Python», .
,
, . , - . - , , , .
« , » — .: , — . , .
, Java , Rust. , Spring , ++.
, — , «» .
, .
—
, , - .
— , — .
« , »
« » « ». , , , , .
, .
,
, , , , .
.
( «»), , , . , AWS SQS ( ), , , RabbitMQ.
- , , , .
,
, . , . , .
( , ). , , , .
,
- , , . , ,
, , .
, . , , , « » « , ».
, .
«». , . , . , , . , .
: «, , , , ». , .
.
. - . «» «».
, , , . , .
, ,
. , , - , .
No hagas esto.
- ,
.
- , . , .
, . .
- ,
: - , . , .
«, , » — , .
, , . . , - . . .
, , . , , , - . , , .
,
« ».
- , , , . : « , , ».
.
, , , . .
, .
«»
«» — . , - « », - .
, , , . , , , .
.
, , «»
. - : «, , ?»
, . , , (, , ).
, —
, -, , , , ( ).
… , - , , «
!».
:
«» , , , , . , , .
, /, .
, .
- .
« » « »
: - , , , .
« » — , .
.
,
, , - , .
- .
, .
, , .
, , , .
… .
IT
.
, , 15 , 3-4 .
.
.
, , , - , , , , , , .
. , , URL, .
Trello — ,
« , », .
,
, « , », « , ».
. . , - .
, .
.
, , .
…
, . , « ». - « », , .
. .
Github «, » . , - .
.
: Python, , Java Python, .
«, »
, , , «, ».
- , , - . , .