Conceptos de Programación más 3 Cheat Sheets
Conceptos de Programación más 3 Cheat Sheets
Fabián Karaben
Buy on Leanpub

Tabla de contenido

Acerca de este libro

Si estás aprendiendo programación y te perdiste en un mundo de conceptos nuevos, o si estás pensando en comenzar a estudiar programación, este libro es para ti.

Este libro forma parte del Proyecto Esdocu (esdocu.com).

¿Qué es el Proyecto Esdocu?

Esdocu es un proyecto de traducción al español de documentación de tecnologías open source, iniciando por algunas de las tecnologías más populares para el desarrollo web y de aplicaciones móviles.

El proyecto pretende no solo realizar las traducciones, sino también mantenerlas siempre actualizadas y sincronizadas con la versión original en inglés.

Esdocu es un proyecto open source en el que cualquier persona puede colaborar a través de los repositorios en nuestra cuenta de GitHub.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Conceptos de programación

Iniciemos

¿Por qué un libro de conceptos?

Algunas personas estudian uno o varios lenguajes de programación por necesidad laborar, otras personas los estudiamos también porque nos apasiona la programación, en cualquier caso luego de aprender dos o tres lenguajes algo se hace evidente, los lenguajes de programación son muy parecidos entre sí.

En este libro aprenderemos los conceptos básicos de programación, conceptos que se encuentran en la mayoría de los lenguajes.

De esta forma buscamos lograr:

  • Un espacio con explicaciones claras y sencillas para aquellas personas que inician en el mundo de la programación.
  • Evitar repetir explicaciones en cada libro volviéndolo tedioso para quienes están estudiando su segundo o tercer lenguaje y ya conocen los conceptos básicos.

¿Por qué existen tantos lenguajes?

Son muchos los motivos por los que existen tantos lenguajes de programación.

Una de las causas es la constante evolución de la tecnología, lo cual genera que aparezcan nuevos lenguajes de programación diseñados para satisfacer las necesidades actuales.

Otro motivo es simplemente la competencia entre diferentes organizaciones por desarrollar un lenguaje que cubra las necesidades de un mercado y que logre convertirse en el lenguaje más utilizado por ese mercado. En estos casos solemos encontrar dos o más lenguajes muy similares y enfocados a realizar las mismas tareas.

Aunque en la actualidad están surgiendo lenguajes que prometen cubrir todos los sectores tecnológicos, como el lenguaje de programación Dart, en el pasado y hasta la actualidad utilizamos lenguajes que fueron creados para un conjunto de propósitos específicos, por ejemplo:

  • El lenguaje JavaScript fue diseñado para el desarrollo de funcionalidades en páginas web.
  • El lenguaje C es muy utilizado para desarrollar funcionalidades de sistemas operativos.

Esto no es una regla estricta, continuando con el ejemplo, se pueden desarrollar funcionalidades web con el lenguaje C y también se podrían desarrollar servicios de sistemas operativos con JavaScript, pero no sería lo más conveniente.

Otros factores como la popularidad pueden definir el sector de aplicación de un lenguaje, ya que es más sencillo trabajar con un lenguaje popular que cuenta con mucha información disponible en Internet, preguntas a dudas y problemas junto con sus respuestas en foros y otros sitios web, además de todo el código que la comunidad comparte bajo licencias Open Source.

Palabras clave

Cuando escribimos código de programación tenemos la posibilidad de definir nombres para ciertos elementos específicos, por ejemplo, al programar una calculadora luego de realizar el primer cálculo necesitaremos un lugar en donde guardar el resultado anterior para aplicarlo en futuros calculos, ese espacio en memoria va a necesitar un nombre para que sepamos dónde encontrar el resultado guardado anteriormente.

El nombre de ese espacio lo elegimos nosotros, puede ser algo que nos ayude a leer fácilmente el código, algo identificativo como resultadoAnterior.

Es en este punto en donde es necesario tener en cuenta las palabras clave de un lenguaje, palabras que están reservadas y que no podemos utilizar para definir nombres en nuestro código. Estas palabras reservadas dependen del lenguaje pero algunos ejemplos típicos son:

  • import, super, default, null, return
  • while, for, if, else, switch, continue
  • class, abstract, static, this
  • try, catch

Comentarios

Los comentarios son simplemente líneas de texto que podemos incluir en nuestro código y que no serán tomadas en cuenta a la hora de ejecutar el programa, es decir que solo es información para documentar un fragmento de código.

Existen comentarios de una sola línea y comentarios multilíneas. La sintáxis depende del lenguaje pero en la mayoría de ellos su utilización es la siguiente.

comentarios.js:
1 // Un ejemplo de comentario en JavaScript (única línea)
2 
3 /* Un ejemplo de comentario
4 multilínea en JavaScript */

Es altamente recomendable documentar con comentarios nuestro código para que sea de utilidad para otros programadores que necesiten entender nuestro trabajo y hasta incluso para ahorrar tiempo analizando código que nosotros mismos escribimos tiempo atrás.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Variables

¿Qué es una variable?

Una variable es un espacio a nuestra disposición en la memoria RAM del dispositivo donde se ejecute nuestro código.

Normalmente se utiliza una variable para almacenar un resultado temporal o valores que necesitaremos utilizar más adelante en ese mismo código. Un ejemplo en el lenguaje JavaScript:

variables.js:
 1 // La siguiente línea define una variable que almacena un número entero.
 2 var primerNumero = 5;
 3 
 4 // La siguiente línea define una variable que almacena un texto.
 5 var primerTexto = "Hola mundo";
 6 
 7 // Segundo número para realizar la suma
 8 var segundoNumero = 7;
 9 
10 /* Una suma utilizando las variables anteriores.
11  * La variable resultado almacena el valor 12.
12  */
13 var resultado = primerNumero + segundoNumero;

Nombres de variables

Los nombres que elijamos para nuestras variables tienen que seguir una serie de reglas que dependen del lenguaje utilizado pero lo típico es que:

  • Deben iniciar con una letra.
  • Pueden contener números pero no al inicio.
  • No pueden ser ninguna de las palabras clave que defina el lenguaje.

También es importante destacar que la mayoría de los lenguajes son sensibles a mayúsculas y minúsculas, por lo tanto:

nombres-variables.js:
1 /* En la mayoría de lenguajes las siguientes
2 variables son distintas y cada una tiene
3 su propio valor asignado */
4 
5 var texto1 = "Primer texto";
6 var TEXTO1 = "Segundo texto";
7 var Texto1 = "Tercer texto";

Alcance de una variable

Al programar normalmente dividiremos nuestro código en partes para organizarlo mejor y agrupar funcionalidades. Estas agrupaciones de código pueden ser en funciones, clases, módulos, etc. (términos que veremos en los siguientes capítulos).

Las variables que definamos solo podrán ser utilizadas dentro de su ámbito, o lo que se conoce como el alcance de una variable. Por ejemplo en el lenguaje Dart:

alcance.dart:
 1 main(){
 2     // Definimos una variable de ámbito global
 3     var unaVariableGlobal = "Soy global";
 4 
 5     // Definimos una función,
 6     // la función llamada 'imprimirVariables'
 7     imprimirVariables() {
 8         // Definimos una variable local
 9         // cuyo ámbito es la función 'imprimirVariables'.
10         var unaVariableLocal = "Soy local";
11 
12         // Esta línea imprimirá en consola 'Soy global'
13         print(unaVariableGlobal);
14 
15         // Esta línea imprimirá en consola 'Soy local'
16         print(unaVariableLocal);
17     }
18 
19     // Esta línea imprimirá en consola 'Soy global'
20     print(unaVariableGlobal);
21 
22     // Esta línea generará un error porque 'unaVariableLocal'
23     // solo es utilizable dentro de la función.
24     print(unaVariableLocal);
25 
26     // Esta línea ejecuta la función imprimirVariables()
27     // la cual imprime 'Soy global' y luego 'Soy local'
28     imprimirVariables();
29 }

Variables por valor y por referencia

Las variables pueden pasarse a una función por valor o por referencia.

Cuando se pasa una variable por valor se realiza una copia de la misma, es decir que al modificar la variable en el interior de la función, esta modificación no va a afectar al valor original de la variable externa.

Por el contrario, cuando se pasa una variable por referencia, al modificar el valor de la variable dentro de la función también se modifica el valor de la variable externa ya que en realidad la segunda es solo una referencia a la primera.

Observemos este ejemplo en el lenguaje de programación Dart:

variables-valor-referencia.dart:
 1 main(){
 2 
 3   // Definimos una variable que almacena un 5
 4   int unNumero = 5;
 5   
 6   // Definimos una variable que almacena el texto 'Soy texto'
 7   String unTexto = "Soy texto";
 8 
 9   // Esta función NO va a modificar la variable 'unNumero',
10   // solo modifica una variable interna a la función.
11   // El parámetro 'elNumero' se pasa por valor.
12   modificarNumero(int elNumero){
13     elNumero = elNumero + 2;
14   }
15 
16   // Esta función modifica la variable 'unTexto'.
17   // El parámetro 'elTexto' se pasa por referencia.
18   modificarTexto(String elTexto){
19     elTexto = elTexto + ' modificado';
20   }
21   
22   // Imprimir los números
23   print(unNumero);
24   modificarNumero(unNumero);
25   print(unNumero);
26   
27   // Imprimir los textos
28   print(unTexto);
29   modificarTexto(unTexto);
30   print(unTexto);
31 }

En Dart los tipos de datos primitivos, como lo es el tipo int, se pasan siempre por valor por ese motivo la variable unNumero no se modifica. Por el contrario el tipo de dato String no es primitivo, se pasa por referencia y por lo tanto el valor de la variable unTexto sí se modifica al llamar a la función modificarTexto.

El ejecutar este código se imprime en la consola de comandos:

Salida:
5
5
Soy texto
Soy texto modificado

Veremos la definición de tipo de dato en el siguiente capítulo pero podemos adelantar que es el tipo del dato que almacena una variable.

En otros lenguajes se puede especificar en cada caso si se desea pasar el parámetro por valor o por referencia, esto sucede por ejemplo en el lenguaje de programación Go.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Tipos de datos

¿Qué es un tipo de dato?

En todos los lenguajes de programación las variables deben definir el tipo de los datos que almacenará. De esta forma es posible predecir su comportamiento, por ejemplo, en JavaScript:

tipos-de-datos.js:
1 // Esta variable almacenará el valor 12
2 var unNumero = 5 + 7;
3 // Esta variable almacenará el valor 15
4 var otroNumero = unNumero + 3;
5 
6 // Esta variable almacenará el valor 'Un texto'
7 var unTexto = "Un" + " texto";
8 // Esta variable almacenará el valor 'Un texto completo'
9 var otroTexto = unTexto + " completo";

Incluso cuando no se especifica el tipo de dato directamente y en su lugar se utiliza la palabra clave var, el tipo de dato es inferido a partir del valor que se le asigna a la variable.

Se puede ver que el operador + se comporta diferente cuando trabajamos con números y cuando trabajamos con textos. Para los número realiza una suma, y para los textos realiza una unión, también conocida como concatenación.

Existen tipos de datos simples y colecciones:

  • Simples: números, textos, verdadero, falso.
  • Colecciones: listas, sets, mapas.

Tipos de datos dinámicos

Muchos lenguajes no requieren definir el tipo de datos de las variables (aunque estas adquieren un tipo de datos igualmente). Por ejemplo en el lenguaje de programación PHP podemos utilizar una variable asignando un valor numérico y luego podemos asignar un valor de texto a esa misma variable sin que se produzca ningún error.

Esto se conoce como Tipado dinámico y pareciera ser muy conveniete pero en la práctica no lo es, cuando la cantidad de líneas de código aumenta es mejor y muy útil dejar claro qué rol cumple una variable y qué tipo de dato va a manejar.

Lenguajes como Dart son de Tipado estático lo que tampoco significa que sea necesario especificar el tipo de dato, por ejemplo:

tipos-de-datos.dart:
 1 // La siguiente variable define explícitamente su tipo de dato.
 2 String unTexto = "Un texto";
 3 
 4 /* La siguiente variable infiere el tipo de dato String
 5 a partir del primer valor que asignamos */
 6 var unTextoInferido = "Un texto inferido";
 7 
 8 /* Las siguientes dos líneas generan un error a causa 
 9 del Tipado estático (no pueden cambiar su tipo de dato) */
10 unTexto = 5;
11 unTextoInferido = 8;

En estos casos recibir un error de tipo de dato nos puede ayudar a detectar que algo no está funcionando como debería.

Números

Normalmente los lenguajes cuentan con por lo menos dos tipos de datos para trabajar con valores numéricos, un tipo para números enteros y otro tipo para números decimales.

Por ejemplo en el lenguaje Dart:

numeros.dart:
1 // Una variable con un valor entero
2 int cantidadDeElementos = 1;
3 
4 // Una variable con un valor decimal
5 double precioTotal = 1.5;

De esta forma podemos elegir el tipo numérico dependiendo del propósito de la variable, lo que aporta comodidad y también mejora el rendimiento ya que trabajar con enteros consume menos recursos del sistema.

Cadenas de texto

Los textos son manejados de forma ligeramente diferente en cada lenguaje de programación, pero normalmente se puede crear y almacenar un texto simplemente colocándolo entre comillas simples o comillas dobles.

Veamos un ejemplo en Dart:

textos.dart:
1 // Dos variables almacenando textos
2 String unTexto = "Solo un texto";
3 String otroTexto = 'Texto entre comillas simples';

Cada lenguaje provee también métodos para manipular textos, como por ejemplo, convertir un texto a mayúsculas o minúsculas, extraer una parte de un texto, comprobar si un texto contiene a otro, etc.

Verdadero o Falso

El tipo de dato booleano también está presente en todos los lenguajes de programación más utilizados. Este tipo sirve para establecer un valor de verdadero o falso, true o false respectivamente.

Un ejemplo en Dart:

booleanos.dart:
1 // Una variable booleana con valor verdadero
2 bool primerBool = true;
3 
4 // Una variable booleana con valor falso
5 bool segundoBool = false;

A simple vista no parece ser muy útil pero utilizaremos mucho valores booleanos para definir el flujo de ejecución del código utilizando también las sentencias condicionales que veremos en un capítulo posterior.

Listas o Arrays

Iniciamos la lección de colecciones viendo las Listas (también conocidas como Arrays en algunos lenguajes).

Las listas son un grupo ordenado de elementos, lo que significa que luego de crear la Lista es posible acceder y modificar un elemento de dicha lista especificando un índice.

Por ejemplo (en Dart):

listas.dart:
 1 // Esta línea crea una Lista de Strings
 2 List unaListaDeTextos = ["uno", "dos", "tres"];
 3 
 4 // Esta línea crea una Lista de números enteros
 5 List unaListaDeEnteros = [1, 2, 3];
 6 
 7 /* Podemos acceder y modificar un valor en la lista 
 8 indicando su índice o posición (la cual inicia desde cero). */
 9 unaListaDeTextos[1] = "DOS";
10 
11 // La siguiente línea imprime 'DOS'
12 print(unaListaDeTextos[1]);
13 
14 // La siguiente línea imprime ['uno', 'DOS', 'tres']
15 print(unaListaDeTextos);

En la mayoría de lenguajes el índice o posición es un número entero e inicia desde cero, aunque algunos lenguajes como PHP unen los conceptos de Lista y Mapas (que veremos pronto) en uno y permiten que sus Listas o Arrays tengan índices de cualquier tipo.

Sets

Los Sets son conjuntos de elementos sin un orden, es decir que a diferencia de las Listas, los Sets no tienen un índice.

Los valores almacenados en un Set deben ser únicos ya que al no poseer un índice se utiliza al propio valor como identificador de un elemento dentro del Set.

Veamos un ejemplo en Dart:

sets.dart:
1 // Esta línea crea un Set de Strings
2 Set frutas = {'Manzana', 'Pera', 'Ciruela'};
3 
4 // Esta línea imprime 'Manzana'
5 print(frutas.first);

Cada lenguaje define formas diferentes para agregar, modificar y eliminar elementos de un Set.

Mapas o Diccionarios

Finalmente un Mapa es un grupo de asociaciones de tipo clave y valor. En otras palabras, un Mapa es similar a una Lista pero en este caso el índice se llama clave y esta clave puede ser de casi cualquier tipo de dato.

Por ejemplo (en Dart):

mapas.dart:
1 // Esta línea define un Mapa de clave String y valor entero.
2 Map temperaturasMaximas = {
3     'verano': 32,
4     'invierno': 3,
5 };
6 
7 // Podemos acceder a un valor del Mapa especificando la clave
8 print(temperaturasMaximas['invierno']);

El valor de los elementos de un Mapa puede ser otro Mapa dando lugar a un Mapa bidimensional.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Funciones

¿Qué es una función?

Una función es un bloque reutilizable de código con un nombre como identificador, una serie de parámetros de entrada y un valor de salida o retorno.

Una vez declarada una función podemos utilizar su nombre para invocarla y así ejecutar el código que contiene una o más veces a lo largo de la ejecución de todo el código.

Un ejemplo de función en Dart:

funciones.dart:
1 sumarNumeros(int primerNumero, int segundoNumero) {
2     return primerNumero + segundoNumero;
3 }

La sintaxis dependerá del lenguaje utilizado pero normalmente el contenido de una función estará encerrado entre llaves, sus parámetros de entrada estarán entre paréntesis justo alado del nombre de la función, y se utilizará la palabra clave return para definir el valor de salida de la función, es decir, el valor que va a retornar al código que invoca a la función.

Parámetros de una función

Otra forma de ver a las funciones es como una caja negra que recibe valores de entrada y devuelve un resultado.

Esos valores de entrada son los parámetros de la función y pueden ser tantos como sean necesarios y de cualquier tipo de dato disponible.

Veámos nuevamente el código del ejemplo anterior en donde la función define dos parámetros de tipo entero que son los número que luego son utilizados para realizar la suma antes de devolver el resultado:

parametros.dart:
1 sumarNumeros(int primerNumero, int segundoNumero) {
2     return primerNumero + segundoNumero;
3 }

Es posible también definir una función sin ningún parámetro de entrada.

Algunos lenguajes permiten que la función tenga una cantidad indefinida de parámetros (en este ejemplo sería una cantidad indefinida de número para sumar), e incluso es posible definir dos o más funciones con el mismo nombre pero con diferente cantidad de parámetros de entrada, lo que diferenciará a una función de otra.

Alcance de una función

De la misma forma que sucede con las variables, las funciones normalmente tienen un alcance definido, es decir que solo podrán ser utilizadas dentro del ámbito donde fueron declaradas.

En el caso de que la función sea pública y parte de una biblioteca externa, también podrá ser utilizada desde todo código que importe esa biblioteca.

Si una función forma parte de una clase, recibe el nombre de método de la clase y podrá ser utilizada desde todo código que utilice esa clase.

Veremos más adelante las explicaciones de los conceptos de biblioteca y clase.

Valor de retorno

Como vimos en los ejemplos anteriores, las funciones normalmente utilizan la palabra clave return para definir el valor que van a devolver.

Una función puede no tener ningún valor de retorno, en ese caso simplemente se omite el uso de la palabra clave return.

Algunos lenguajes como por ejemplo Go, permiten retornar uno, dos o más valores a la vez, en los demás lenguajes si se necesita devolver más de un valor, se puede utilizar una Lista o un Mapa como valor de retorno.

Ejecutar una función

Ahora que vimos cómo definir una función, veámos un ejemplo de invocación de una función lo cual toma los valores de entrada, los utiliza al ejecutar el contenido de la función y luego opcionalmente retorna un resultado.

Ejemplo completo en Dart:

ejecucion-de-funciones.dart:
 1 main(){
 2     // Definición de una función para sumar dos número enteros
 3     sumarNumeros(int primerNumero, int segundoNumero) {
 4         return primerNumero + segundoNumero;
 5     }
 6     
 7     // Ejecuta la función y almacena el resultado en una variable
 8     int resultado = sumarNumero(4, 5);
 9     
10     // Imprime el resultado de sumar 4 + 5
11     print(resultado);
12 }

En este ejemplo la función main() es simplemente un requerimiento particular del lenguaje Dart en el cual todo programa inicia su ejecución con una función con este nombre.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Operadores y expresiones

Operadores aritméticos

Todos los lenguajes poseen una lista de operadores para realizar operaciónes matemáticas:

Operador Descripción
+ Suma
- Resta
* Multiplicación
/ División

También es normal contar con operadores para realizar divisiones enteras, así como cálculos del resto de una división, aunque estos operadores suelen diferir según el lenguaje.

La utilización de estos operadores es tan sencilla como el siguiente ejemplo:

operadores-aritmeticos.dart:
1 // Ejemplos simples de operadores aritméticos
2 var resultadoSuma = 3 + 2;
3 var resultadoResta = 5 - 2;
4 var resultadoMultiplicacion = 5 * 2;
5 var resultadoDivision = 10 / 2;

Solo requiere que los operandos sean numericos, ya que por ejemplo el operador + suele tener otro comportamiento si se lo utiliza con cadenas de texto (Strings).

Operadores de equidad y relación

Veremos las sentencias condicionales y los bucles en un capítulo posterior y solo entonces será evidente la utilidad de los operadores necesarios para realizar comparaciones, por el momento analizaremos un ejemplo sencillo para exponer el funcionamiento de estos operadores.

Los operadores de equidad y relación nos permiten saber si dos valores son iguales, distintos, o poseen una de las relaciones en la siguiente tabla:

Operador Descripción
== Igualdad
!= Desigualdad
> Mayor
< Menor
>= Mayor o igual
<= Menor o igual

Estos operadores son ampliamente utilizados para definir el flujo de ejecución de un programa, por ejemplo:

operadores-equidad-relacion.dart:
1 int unNumero = 5;
2 
3 if (unNumero > 3) {
4 	print("El número es mayor a 3");
5 }

En este ejemplo la función print() solo se ejecuta si se cumple la condición de la sentencia if, es decir, si el número en la variable unNumero es mayor a 3.

Operadores de asignación

Hemos utilizado un operador de asignación desde los primeros ejemplos, el operador = que usamos para asignar un valor a una variable.

Normalmente los lenguajes incluyen otros operadores de asignación. Los siguiente son ejemplos típicos:

Operador Ejemplo Expresión equivalente
+= variable += 2; variable = variable + 2;
-= variable -= 2; variable = variable - 2;
*= variable *= 2; variable = variable * 2;
/= variable /= 2; variable = variable / 2;
++ variable++ variable = variable + 1;
variable– variable = variable - 1;

Operadores lógicos

Los operadores lógicos son muy útiles para combinar dos o más comparaciones.

Los operadores lógicos típicos son:

Operador Descripción
! Negación
&& AND
|| OR

Veamos algunos ejemplos:

operadores-logicos-and.dart:
1 int primerNumero = 5;
2 int segundoNumero = 10;
3 
4 if (primerNumero == 5 && segundoNumero > 8) {
5     print("Se cumplen ambas condiciones");
6 }

En este ejemplo la función print() se ejecuta únicamente si las dos condiciones en la sentencia if son verdaderas.

operadores-logicos-or.dart:
1 int primerNumero = 5;
2 int segundoNumero = 10;
3 
4 if (primerNumero == 5 || segundoNumero > 8) {
5     print("Se cumple una o ambas condiciones");
6 }

En este ejemplo la función print() se ejecuta si se cumple la primera, la segunda o ambas condiciones en la sentencia if, es decir que únicamente se evita ejecutar la función print() si no se cumplen ambas condiciones.

Expresión condicional

Muchos lenguajes incluyen una expresión condicional la cual permite optar por un valor u otro dependiendo del cumplimiento de una condición data, por ejemplo:

expresiones-condicionales.dart:
1 var numeroComoTexto = "dos";
2 
3 // Expresión condicional
4 var resultado = (numeroComoTexto == "dos") ? 2 : 0;

En este ejemplo si se cumple la condición entre paréntesis, la variable resultado recibe el valor 2, caso contrario recibe el valor 0.

Expresiones regulares

Las expresiones regulares nos permiten evaluar una condición compleja de forma directa y rápida.

La sintaxis de las expresiones regulares varía ligeramente de un lenguaje a otro, por lo que estudiaremos los detalles en su respectivos libros, por el momento veamos un ejemplo de validación de email utilizando expresiones regulares en el lenguaje Dart:

expresiones-regulares.dart:
 1 /* Una variable de tipo de dato 'RegExp' y nombre 'emailRegExp',
 2 almacenando la expresión regular que define la sintaxis de
 3 una dirección de email válida */
 4 var RegExp emailRegExp = RegExp(
 5     r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
 6 );
 7 
 8 // Una dirección de email de ejemplo
 9 var unEmail = "name@company.com";
10 
11 /* La función 'hasMatch' de Dart comprueba que la dirección
12 de email en 'unEmail' cumple los criterios de la expresión
13 regular en la variable 'emailRegExp' */
14 if(emailRegExp.hasMatch(unEmail)){
15     print("El email es válido.");
16 }

En este ejemplo validar un email parte por parte requeriría muchas líneas de código, en cambio con expresiones regulares se puede realizar de forma directa y rápida, aunque a simple vista una expresión regular pueda ser intimidante, tiene un gran potencial que vale la pena aprovechar.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Sentencias de control de flujo

Sentencia if…else

Prácticamente todos los lenguajes basan su flujo de ejecución en el cumplimiento de un conjunto de condiciones.

La sentencia más simple a la hora de establecer un condicional de ejecución es la sentencia if.

Repasemos un ejemplo ya visto (en Dart):

sentencia-if.dart:
1 int unNumero = 5;
2 
3 // Condicional simple
4 if (unNumero > 3) {
5     print("El número es mayor a 3");
6 }

En este ejemplo la función print se ejecuta únicamente si se cumple la condición de la sentencia if, es decir, si el número es mayor a 3. Si la condición no se cumple, la ejecución simplemente ignora el contenido entre llaves (conocido como el bloque o cuerpo de la sentencia if).

También se puede establecer un bloque de código para el caso en el que no se cumpla la condición, por ejemplo:

sentencia-if-else.dart:
1 int unNumero = 5;
2 
3 if (unNumero > 3) {
4     print("El número es mayor a 3");
5 } else {
6     print("El número no es mayor a 3");
7 }

E incluso es posible definir una o más condiciones adicionales, por ejemplo:

sentencia-if-elseif-else.dart:
1 int unNumero = 5;
2 
3 if (unNumero > 3) {
4     print("El número es mayor a 3");
5 } else if (unNumero == 3){
6     print("El número es igual a 3");
7 } else {
8     print("El número no es mayor o igual a 3");
9 }

En este caso la segunda condición solo se evalúa si la primer condición no se cumplió, y el bloque else solo se ejecuta si las dos condiciones anteriores no se cumplieron.

Hay que aclarar que cada lenguaje tiene su propia sintaxis y algunas pequeñas diferencias, pero este es el funcionamiento típico que encontraremos siempre.

Sentencia switch/case

Cuando una sentencia if posee muchas condiciones adicionales else if, es posible que sea conveniente utilizar en su lugar una sentencias switch/case:

sentencia-switch.dart:
 1 var fruta = "Manzana";
 2 
 3 switch (fruta) {
 4     case 'Pera':
 5         print("Es una pera");
 6         break;
 7     case 'Manzana':
 8         print("Es una manzana");
 9         break;
10     case 'Ciruela':
11         print("Es una ciruela");
12         break;
13     default:
14         print("Es una fruta misteriosa");
15 }

La sentencia switch en este ejemplo comparará la variable fruta con el valor definido para cada case, y en el caso de producirse una coincidencia ejecutará las lineas posteriores hasta encontrar una sentencia break. Si no se encuentra ninguna coincidencia case, se ejecutarán las lineas posteriores a default.

Veremos la definición de la sentencia break pronto, por el momento en este ejemplo break produce que la ejecución salga completamente del bloque switch y continúe en la línea siguiente al mismo.

Bucles for

Es normal encontrarnos en un escenario en el que necesitamos ejecutar repetidamente un bloque de código, si el número de repeticiones necesarias es conocido, podemos utilizar un bucle for como se muestra en el siguiente ejemplo:

bucle-for.dart:
1 // Ejecutar este bloque desde i = 1 hasta i = 999 veces
2 for (var i = 1; i < 1000; i++) {
3 	print("Repetición número " + i);
4 }

El resultado de este ejemplo será:

Salida:
Repetición número 1
Repetición número 2
Repetición número 3
...
Repetición número 999

Esto nos evita escribir 999 veces la línea de la función print.

El contenido entre paréntesis de la definición de este bucle for se divide en tres partes:

  • la primer parte var i = 1 define una variable con un valor inicial de 1,
  • la segunda parte i < 1000 es la condición que se evaluará antes de cada repetición, si esta condición es verdadera se ejecuta el código contenido en el bloque del bucle for.
  • la tercer parte i++ aumenta en una unidad el valor de la variable i luego de cada ejecución del bloque del bucle.

El bucle finaliza cuando la condición de la segunda parte no se cumpla.

Bucles while

El bucle while es similar en funcionalidad al bucle for, pero es más cómodo de utilizar cuando no es necesario que el bucle defina su propia variable para manejar la condición de finalización del bucle, o cuando el número de repeticiones no se conoce.

Veamos un ejemplo de bucle while en Dart:

bucle-while.dart:
 1 // Una lista o array de frutas
 2 List frutas = ['Manzana', 'Pera', 'Ciruela', 'Limón', 'Naranja'];
 3 
 4 // Indice necesario para recorrer la lista de frutas
 5 int indice = 0;
 6 
 7 // Repetir mientras la fruta no sea 'Limón'
 8 while(frutas[indice] != 'Limón') {
 9     indice += 1;
10 }
11 
12 // Registrar el resultado
13 int cantidad = indice;
14 
15 // Imprimir el resultado
16 print("Cantidad de frutas antes del Limón:");
17 print(cantidad);

El bloque del bucle while se ejecutará repetidamente siempre que la condición entre paréntesis continúe siendo verdadera.

En este caso es necesario aumentar en una unidad la variable indice dentro del cuerpo o bloque del bucle, si olvidaramos realizar esto, estaríamos frente a un bucle infinito lo cual suele provocar que el programa deje de responder mientras se ejecuta.

Break y continue

Por último en esta lección vamos a ver el uso de las sentencias break y continue.

Utilizaremos break para interrumpir completamente la ejecución de una sentencia switch, un bucle for o un bucle while, por ejemplo:

palabra-clave-break.dart:
1 for (var i = 0; i < 50; i++) {
2     // Evaluar si corresponde interrumpir
3     if (i == 5) {
4         // Interrumpir el bucle for
5         break;
6     }
7     print(i);
8 }

La salida de este código será:

Salida:
0
1
2
3
4

Al llegar al valor 5, el condicional if se cumple, se ejecuta la sentencia break y la ejecución del código continúa justo en la línea de código que exista luego de la llave de cierre del bucle for.

Utilizaremos continue para saltar únicamente la repetición actual, por ejemplo:

palabra-clave-continue.dart:
1 for (var i = 0; i < 5; i++) {
2     // Evaluar si corresponde realizar un salto
3     if (i == 3) {
4         // Saltar esta repetición
5         continue;
6     }
7     print(i);
8 }

La salida de este ejemplo será:

Salida:
0
1
2
4

Cuando la variable i es igual a 3 la ejecución de esa repetición se interrumpe con la sentencia continue y por lo tanto no se ejecuta la función print para ese valor.

Este es el comportamiento que encontraremos en la mayoría de lenguajes tanto para sentencias condicionales como para bucles, aunque la sintaxis será ligeramente diferente en cada caso.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Excepciones

¿Qué es una excepción?

Las excepciones son el mecanismo que utiliza la mayoría de lenguajes para permitirnos controlar eventualidades que se aparten del correcto funcionamiento de un programa.

Por ejemplo al solicitar información de un recurso en Internet, puede ocurrir que temporalmente ese recurso no esté disponible. Estas son cuestiones que no podemos preveer en el momento de la programación y por tal motivo los lenguajes nos permiten escribir código para definir comportamientos que puedan controlar esas eventualidades, por ejemplo se puede cancelar una transacción o se puede mostrar un mensaje al usuario informando que el recurso no está disponible en ese momento.

El ejemplo de solicitar información desde Internet es el más típico pero la captura y manejo de excepciones se utiliza en una gran variedad de casos.

Veremos ejemplos de control de excepciones en la siguiente pantalla.

Try y catch

Las funciones incluidas en los lenguajes, al producirse una irregularidad durante la ejecución del código, normalmente lanzarán una excepción que nosotros podemos capturar y manejar.

Veamos un ejemplo en el lenguaje Dart:

try-y-catch.dart:
1 try {
2     // Ejemplo de consulta de contenido en Internet.
3     print(await http.read('https://example.com/foobar.txt'));
4 } catch (e) {
5     // Notificar que algo salió mal y los detalles en 'e'.
6     print('Algo salió mal, detalles: $e');
7 }

Prestemos atención a las líneas try y catch. Dentro del bloque try colocamos el código que podría lanzar una excepción, y dentro del bloque catch colocamos el código para controlar esa posible excepción.

En este ejemplo si por alguna razón el recurso remoto no se encuentra disponible, se ejecutará la función print de la línea 6.

Throw

La sentencia throw se utiliza para lanzar una excepción.

Al igual que lo hacen las funciones integradas en los lenguajes, si escribimos una función que pueda encontrarse con una eventualidad problemática, podemos lanzar nuestra propia excepción personalizada para que el código que utilice esta función pueda capturarla.

Veamos un ejemplo del lanzamiento de una excepción personalizada en Dart:

throw.dart:
1 if (algunProblema) {
2     throw 'Aquí describiremos el problema';
3 }

También podemos utilizar las excepciones personalizadas para capturar un conjunto de excepciones con detalles técnicos y lanzar en su lugar nuestra propia versión de excepción con un mensaje más amigable que se pueda mostrar directamente al usuario final.

Lenguajes sin try y catch

Algunos lenguajes no poseen las sentencias try y catch, en su lugar desarrollan otros métodos para controlar los errores.

Por ejemplo en el lenguaje de programación Go el control de errores tiene la siguiente forma:

errores.go:
1 val, err := unaFuncionCualquiera();
2 
3 if err != nil {
4 	// handle error
5 } else {
6 	// success
7 }

En Go una función puede devolver más de un valor, y por lo tanto normalmente las funciones devuelven un primer valor con el resultado de la operación, y devuelven un segundo valor que puede ser el detalle de un error producido o puede ser el valor nil el cual significa que no se produjo ningún error.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Clases

¿Qué es una clase?

Programar puramente con funciones puede presentar algunos problemas sobre todo si varias funciones comparten recursos como variables globales. Además, en estos casos, cuando un desarrollo crece el código tiende a volverse caótico. Para solucionar esta problemática surge el concepto de Clases.

Las clases en programación son simplemente una forma de organizar el código en entidades que aportan aislamiento a su código interno, y proveen métodos y propiedades para comunicarse con el código externo a esa clase.

Se suele ver a las clases como una analogía a objetos de la vida real. Si tomamos como ejemplo una puerta:

  • la clase se llamaría ‘Puerta’,
  • sos propiedades serían: color, altura, grosor, …
  • y sus métodos serían: cerrar pueta, abrir puerta, …

Los métodos no son más que funciones como las que ya conocemos pero en este caso son funciones que pertenecen a la clase, y las propiedades de una clase no son más que variables que pertenecen a la clase.

Además, una clase puede configurar la privacidad de métodos y propiedades, es decir, permitir o no que el código externo a la clase pueda acceder a estos elementos.

Para utilizar una clase se debe crear una o más instancias de la misma. La instancia de una clase se llama un Objeto de clase. Veremos un ejemplo en la siguiente pantalla.

Propiedades

Las propiedades son variables pertenecientes a una clase. Veamos un ejemplo en Dart:

propiedades.dart:
 1 // Definimos la clase 'Puerta'
 2 class Puerta {
 3     // Definimos una propiedad para esta clase
 4     String color;
 5 }
 6 
 7 // Creamos una instancia de la clase 'Puerta'
 8 var unaPuerta = Puerta();
 9 
10 // Cambiamos el valor de la propiedad 'color'
11 unaPuerta.color = 'rojo';

En este ejemplo color es una propiedad de la clase, a la cual podemos acceder utilizando la notación de punto luego de crear el objeto unaPuerta (instancia de la clase Puerta).

Métodos

Los métodos son funciones pertenecientes a una clase. Veamos un ejemplo en Dart:

metodos.dart:
 1 // Definimos la clase 'Puerta'
 2 class Puerta {
 3     // Definimos una propiedad para esta clase
 4     String estado = 'cerrada';
 5     
 6     // Definimos un método para abrir la puerta
 7     abrir(){
 8         estado = 'abierta';
 9     }
10     
11     // Definimos un método para cerrar la puerta
12     cerrar(){
13         estado = 'cerrada';
14     }
15 }
16 
17 // Creamos una instancia de la clase 'Puerta'
18 var unaPuerta = Puerta();
19 
20 // Cambiamos el estado de la puerta utilizando un método
21 unaPuerta.abrir();

En este ejemplo abrir y cerrar son métodos de la clase, a los cuales podemos acceder utilizando la notación de punto luego de crear el objeto unaPuerta (instancia de la clase Puerta).

Los lenguajes tienen diferentes formas de configurar una propiedad como privada, en este ejemplo lo ideal sería que la propiedad estado sea privada para que no se pueda modificar directamente desde fuera de la clase.

Claro que este es un ejemplo muy sencillo en el que solo estamos cambiando el valor de una propiedad, aun así se puede observar la estructura básica de una clase y la utilización de propiedades y métodos.

Constructores

El constructor de una clase es una función que se ejecutará justo en el momento en el que creamos una instancia de la clase. Normalmente no se lo utiliza para tareas complejas sino más bien para tareas sencillas como por ejemplo asignar valores por defecto a propiedades.

Veamos un ejemplo en el lenguaje Dart:

constructores.dart:
 1 // Definimos la clase 'Puerta'
 2 class Puerta {
 3     // Definimos una propiedad para esta clase
 4     String estado;
 5     
 6     // Constructor de la clase
 7     Puerta() {
 8         estado = 'cerrada';
 9     }
10 }

En Dart el constructor de una clase es un método con el mismo nombre que la clase.

Clases abstractas

Una clase abstracta solamente define propiedades y métodos sin especificar su implementación, es decir, se omite el cuerpo de los métodos.

Esto es muy útil para definir una especie de “molde” a partir del cual se pueden construir otras clases específicas.

Este concepto se entenderá mejor con un ejemplo en Dart:

clases-abstractas.dart:
 1 // Definición de una clase abstracta
 2 abstract class Animal {
 3     // Un método abstracto no define el cuerpo.
 4     correr();
 5 }
 6 
 7 // Definición de una clase que extiende una clase abstracta
 8 class Leon extends Animal {
 9 	correr(){
10         /* Aquí implementaríamos la manera en la que
11         corre un león */
12     }
13 }
14 
15 // Definición de una clase que extiende una clase abstracta
16 class Avestruz extends Animal {
17 	correr(){
18         /* Aquí implementaríamos la manera en la que
19         corre un avestruz */
20     }    
21 }
22 
23 // Creamos una instancia de la clase 'Leon'
24 Animal unAnimal = Leon();
25 
26 // Creamos una instancia de la clase 'Avestruz'
27 Animal otroAnimal = Avestruz();
28 
29 // Hacer correr al león
30 unAnimal.correr();

De esta forma tanto la variable unAnimal como la variable otroAnimal son del tipo de dato Animal y como las clases Leon y Avestruz extienden a la clase Animal entonces ambas variables pueden almacenar un objeto de tipo Leon o de tipo Avestruz.

Un ejemplo práctico para esto sería solicitar al usuario que seleccione un animal, para lo cual la misma variable podría almacenar a cualquier animal que el usuario elija sin necesidad de definir una variable para cada animal.

Extendiendo clase

Acabamos de ver cómo extender una clase abstracta, pero también es posible extender una clase normal no abstracta, con lo cual la clase que extiende hereda todas las propiedades y métodos de la clase extendida.

Veamos un ejemplo en Dart:

extendiendo-clases.dart:
 1 // Definición una clase normal
 2 class Mueble {
 3     String color;
 4 }
 5 
 6 // Definición una clase que extiende la clase anterior
 7 class Mesa extends Mueble {
 8 }
 9 
10 // Creamos una instancia de la clase 'Mesa'
11 var unaMesa = Mesa();
12 
13 // Asignamos un color al objeto 'unaMesa'
14 unaMesa.color = 'negro';

De esta forma se puede reutilizar código ya que otras clases también pueden extender la clase Mueble, por ejemplo una clase Silla, la cual también tendrá una propiedad color.

Las clases que extienden también pueden definir sus propias propiedades y métodos.

En los libros de cada lenguaje veremos cómo cada uno de ellos define diferentes formas de sobreescribir propiedades y métodos de la clase extendida.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Bibliotecas, paquetes o módulos

¿Qué son las bibliotecas?

Las bibliotecas son paquetes de funcionalidades que separamos de nuestro código principal, o funcionalidades que directamente programamos de forma separada para que puedan ser reutilizadas por otros desarrollos.

Dependiendo del lenguaje las bibliotecas también reciben el nombre de paquetes, módulos o plugins.

Bibliotecas integradas

Normalmente los lenguajes de programación buscan ser lo más sencillos posible, definiendo componentes básicos y dejando las funcionalidades específicas en bibliotecas opcionales que se pueden añadir según la necesidad del código.

Por ejemplo en el lenguaje Dart, podemos utilizar la biblioteca math para acceder a constantes y funciones matemáticas:

bibliotecas-integradas.dart:
1 // Importamos la biblioteca opcional 'math'
2 import 'dart:math' as math;
3 
4 main(){
5     // Imprimir el valor de la constante PI.
6     print(math.pi);
7 }

No se trata de un paquete externo, sino de una biblioteca incluída en el lenguaje pero que requiere importala manualmente cuando sea necesario.

Bibliotecas de la comunidad

Sobre todo en lenguajes Open Source, la cantidad de recursos y funcionalidades que aporta la comunidad es enorme.

Siguiendo con ejemplos del lenguaje Dart, cuando necesitamos una funcionalidad que no se encuentra en el propio lenguaje, podemos buscar un paquete externo en:

pub.dev

y en caso de encontrarlo, simplemente lo agregamos a nuestro desarrollo, lo importamos y usamos como se muestra a continuación:

bibliotecas-comunidad.dart:
1 import 'package:http/http.dart' as http;
2 
3 main() {
4     // Ejemplo de consulta remota usando el paquete 'http'
5     print(await http.read('https://example.com/foobar.txt'));
6 }

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Programación asíncrona

¿Qué es la programación asíncrona?

Programación asíncrona se refiere a aquellas operaciones que un desarrollo o aplicación realiza sin detener el funcionamiento de dicha aplicación.

El ejemplo más sencillo de explicar es el de las consultas remotas, es decir, cuando una aplicación solicita información a un servidor remoto, la aplicación sigue su curso realizando otras tareas sin generar bloqueos, luego cuando los datos remotos llegan la aplicación realiza la tarea correspondiente para actualizar la interfaz con los nuevos datos.

Aunque el ejemplo de los datos remotos es el más sencillo de graficar, una operación asíncrona también puede ser la lectura de un archivo local, la realización de un cálculo que pueda demorar algunos segundos en finalizar, entre muchos otros casos útiles.

Futures o Promises

Hablando ahora de aspectos técnicos, la forma en la que los lenguajes de programación manejan las operaciones asíncronas es con la utilización de Futures (o Promises según el lenguaje).

Cuando se inicia una operación asíncrona se obtiene un Future y la ejecución continúa con otras tareas, luego cuando ese Future recibe datos para mostrar, ya sea contenido o un mensaje de error, se inicia una acción de respuesta normalmente ubicada en el método then.

Veamos un ejemplo en Dart:

futuros.dart:
1 import 'package:http/http.dart' as http;
2 
3 main() {
4     // Ejemplo de consulta remota usando el paquete 'http'
5     http.read('https://example.com/foobar.txt').then((data){
6         print('Datos recibidos.');
7     });
8     print('Sin esperas.');
9 }

Este código inicia una solicitud remota utilizando la función read del paquete http, función que devuelve un Future, e inmediatamente después y sin esperas se ejecuta la función print con el siguiente mensaje:

Salida:
Sin esperas

y luego de un tiempo (normalmente un segundo o menos), la petición remota finaliza devolviendo los datos obtenidos y entonces se ejecuta el contenido del método then con lo que en la consola veremos el mensaje:

Salida:
Datos recibidos.

Existe otro método más elegante para manejar Futures y consiste en utilizar async y await, pero el resultado es el mismo por lo que veremos esa metodología y todos sus detalles en el curso de Dart y en otros libros de lenguajes que también funcionan de esa manera, como el lenguaje JavaScript.

Streams

Los Streams son muy similares a los Futures con la diferencia de que un Stream establece un vinculo con la fuente por lo tanto si dicha fuente de datos es modificada, se envía una notificación al código que inició el Stream.

Este vínculo permanece activo hasta que se lo cierre manualmente, por lo que la fuente de datos puede enviar múltiples notificaciones separadas en el tiempo.

Un ejemplo típico de utilización de un Stream es el desarrollo de un chat en línea, para evitar realizar consultas repetitivas para conocer si existen nuevos mensajes, simplemente se establece un Stream y entonces es la fuente (en este ejemplo un servidor remoto) la que notificará a la aplicación que existen nuevos mensajes para visualizar.

Por ahora es suficiente con entender este concepto, veremos ejemplos concretos para cada lenguaje en sus correpondientes libros.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Lenguajes compilados e interpretados

Lenguajes compilados

Los lenguajes compilados son aquellos que previo a la ejecución del código desarrollado requieren que dicho código se convierta al lenguaje máquina, es decir, a código binario nativo del dispositivo o plataforma donde se lo va a ejecutar. Este proceso de conversión se llama compilación.

Por ejemplo, si compilamos nuestro código para ejecutarlo en el sistema operativo Windows, se generará un archivo .exe que podremos ejecutar directamente.

Las ventajas de los lenguajes compilados son:

  • suelen incluir todas las dependencias (las bibliotecas externas que utiliza) en el propio archivo ejecutable,
  • su ejecución es muy rápida ya que el código binario se optimiza para su ejecución nativa en comparación con el código interpretado que definiremos en la siguiente pantalla.

La desventaja de un lenguaje compilado es que el proceso de compilación hace del ciclo de desarrollo (escritura/compilación/testeo/depuración) un poco más lento que al trabajar con un lenguaje interpretado.

Lenguajes interpretados

Los lenguajes interpretados no requieren del proceso de conversión a código binario (compilación), pero en su lugar requiren de un software que tome el código fuente y lo ejecute, hablamos del intérprete.

El intérprete recorre el código y lo ejecuta línea a línea a la vez que lo lee.

Esto hace que el proceso de desarrollo sea muy ágil, ya que simplemente se edita el código y se lo vuelve a ejecutar.

Por otra parte los lenguajes interpretados tienden a ser más lentos en ejecución que los lenguajes compilados.

La elección entre lenguaje compilado o lenguaje interpretado se basa en la necesidad particular de la aplicación a desarrollar, en algunos casos es importante la máxima optimización en la ejecución y en otros casos es más importante la optimización del tiempo de desarrollo.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Línea de comandos vs UI

Línea de comandos

Todos los sistemas operativos incluyen una herramienta para trabajar con comandos del sistema, esa herramienta es la consola de comandos o terminal.

Normalmente cuando programamos necesitamos de la consola de comandos para ejecutar el programa (y también para compilarlo si el lenguaje es compilado).

Por ejemplo, para ejecutar un archivo con código del lenguaje interpretado Python utilizaríamos el siguiente comando:

Terminal:
python micodigo.py

En este ejemplo python es el intérprete y micodigo.py es el archivo con el código Python.

Por el momento solo veremos este concepto, los comandos utilizados para cada lenguaje junto con los pasos para ingresar a la consola de comandos los veremos en el libro correspondiente a cada lenguaje.

Entrada y salida de datos

Cuando un software está pensado para ser utilizado desde la consola de comandos, este normalmente puede recibir valores de entrada y exponer valores de salida.

Incluso anque el software no esté pensado para recibir valores de entrada, podemos utilizar la salida a consola para imprimir valores de prueba de la misma forma en la que lo hicimos en gran parte de los ejemplos vistos en el libro cuando utilizamos la función print() del lenguaje Dart.

Volviendo al ejemplo de la ejecución de un software en Python, si hablamos de un código que recibe un nombre e imprime un saludo, el comando en consola y su salida se verían como en el siguiente ejemplo:

Terminal:
python saludo.py Juan
Hola Juan

La primer línea es el comando de ejecución y la segunda línea es la salida en consola que se produce.

UI web

Una interfaz de usuario web permite a un usuario interactuar con el software utilizando un navegador web.

Esta interfaz web estará construida con elementos HTML, con estilos CSS, y con funcionalidades en el lenguajes JavaScript. (Más información en el libro HTML+CSS y en el libro JavaScript).

Hoy en día no es completamente necesario aprender HTML, CSS y JavaScript. Existen lenguajes como TypeScript o Dart que permiten convertir su código a JavaScript optimizado.

También existen tecnologías como Flutter, la cual se encarga de todos los aspectos anteriormente mencionados, permite programar en un solo lenguaje (Dart) y compilar el código para múltiples plataformas, incluyendo la plataforma web.

UI de escritorio

Existen muchas bibliotecas para desarrollar interfaces gráficas para software de escritorio, es decir, software que se puede instalar o directamente ejecutar en un sistema operativo Windows, Linux o macOS.

Algunas de estas bibliotecas son específicas de un sistema operativo particular y otras son completamente multiplataforma.

Algunos ejemplos son:

  • GTK+
  • Qt
  • Swing
  • Unity

Veremos definiciones, ejemplos y codelabs de ellas en los libros de cada lenguaje.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

¿Por dónde continuar?

Elegir según la aplicación

A la hora de iniciar un proyecto una de las decisiones a tomar es qué lenguaje de programación utilizar.

Como mencionamos en el apartado de lenguajes compilados y lenguajes interpretados, si necesitamos que nuestro código sea óptimo en tiempo de ejecución, lo más conveniente es elegir un lenguaje compilado. Si por el contrario la prioridad es optimizar el tiempo de desarrollo, lo más conveniente puede ser un lenguaje de programación interpretado.

Otro factor a tener en cuenta es la información disponible en Internet para un lenguaje en relación al propósito del desarrollo del proyecto. Si un lenguaje se populariza para desarrollar un determinado tipo de aplicaciones, en Internet se podrá encontrar fácilmente cientos o miles de preguntas a dudas y problemas típicos junto a sus respuestas en foros y blogs, y también se podrán encontrar bibliotecas desarrolladas por la comunidad que pueden hacer nuestro trabajo mucho más fácil.

Elegir según la demanda laboral

Desde otra perspectiva, si lo que buscamos es formarnos como programadores para luego insertarnos en el mercado laboral, lo conveniente es aviriguar qué lenguajes son los más solicitados por las empresas en ese momento.

Esto suele variar año a año por lo que lo más útil es realizar una búsqueda en Google y consultar varias fuentes. Siempre se pueden econtrar artículos de blogs con información actualizada.

Saludo final

Hasta aquí el libro de Conceptos de programación, esperamos que haya sido claro y de utilidad. Si ves algún error o una sección que te parezca que no explica claramente, por favor envíanos un comentario.

Nos vemos en el siguiente libro.

Consigue la última versión

Este libro se actualiza frecuentemente corrigiendo errores y agregando nuevo contenido.

Puedes descargar gratis la versión más reciente de este libro directamente desde leanpub.com.

También puedes enviar tus sugerencias o avisarnos de algún error que hayas encontrado, utilizando este formulario de contacto.

Cheat sheets

Cheat sheet de Git

Configuración inicial

Después de instalar Git lo primero que hay que hacer es configurar el usuario con nuestro nombre e email para que el log registre correctamente quién realizó cada commit.

Terminal:
$ git config --global user.name "Tu nombre va aquí"
$ git config --global user.email "email@example.com"

Para modificar la configuración en un editor:

Terminal:
$ git config --global -e

Imprimir la configuración en consola:

Terminal:
$ git config --global -l

Alias

Esto depende de preferencias personales, pero estos son los dos alias que utilizo. El primero es un alias para el comando log personalizado y el segundo es un alias para el comando status personalizado.

Terminal:
$ git config --global alias.lg "log --oneline --decorate --all --graph"
$ git config --global alias.s "status -s -b"

Ahora podemos utilizar estos alias de la siguiente manera:

Terminal:
$ git lg
Terminal:
$ git s

Diff

Ver las modificaciones en archivos tracked que todavía no están en el stage, es decir, las diferencias entre el commit anterior y el momento actual:

Terminal:
$ git diff

Para ver las modificaciones en archivos que ya se encuentran en el stage:

Terminal:
$ git diff --staged

Quitar un archivo del stage:

Terminal:
$ git reset HEAD README.md

Nota: lo quita del stage porque reset por defecto es --mixed

Reconstruir todo el repositorio a como estaba en el último commit:

Terminal:
$ git checkout -- .

o bien

Terminal:
$ git reset --hard

Nota: es recomendado usar git checkout -- ., es más seguro. Ver las diferencias en: https://stackoverflow.com/questions/32230161/difference-between-git-reset-hard-and-git-checkout.

Reconstruir un solo archivo a como estaba en el último commit:

Terminal:
$ git checkout -- README.md

Cambiar el mensaje del último commit

Terminal:
$ git commit amend -m "Nuevo mensaje"

Ignorar el último commit (con –soft) cuando faltó agregar más contenido

Terminal:
$ git reset --soft HEAD^

Antes de ejecutar reset:

- HEAD es el punto actual, después del commit que queremos ignorar.
- HEAD** el commit anterior al último. - **HEAD2 dos commits anteriores al último, y así.
- En lugar de HEAD se puede usar un hash obtenido en el log. Hash del commit que quedará como el más reciente luego del reset.
- –soft significa que no se van a revertir los cambios en el directorio de trabajo, solo se ignora la existencia del commit o de los commits indicados.

Ahora ya se puede hacer un nuevo commit que incluya las modificaciones que habían faltado.

Ignorar el último commit (con –mixed) cuando faltó agregar más contenido

Terminal:
$ git reset --mixed HEAD^

--mixed es muy similar a --soft con la diferencia de que también saca todos los cambios del stage.

Ignorar el último commit (con –hard)

Terminal:
$ git reset --hard HEAD^

--hard es destructivo, va a dejar todo el repositorio como estaba en el momento del commit indicado

Nota: igualmente se puede recuperar utilizando reflog

Ver el log de absolutamente todo lo que ha sucedido en orden cronológico

Terminal:
$ git reflog

Cada item en el log tiene un hash que se puede utilizar para hacer reset.

Nota: incluso se puede volver al punto anterior al merge de una rama

Crear una rama, trabajar en ella y luego unirla a main

Estando en main, creamos la rama:

Terminal:
$ git branch nueva-rama

nos movemos a la nueva rama:

Terminal:
$ git checkout nueva-rama

o bien la creamos y nos movemos al mismo tiempo:

Terminal:
$ git checkout -b nueva-rama

Nota: si por error se hicieron modificaciones estando en la rama main antes de crear la rama secundaria, no es ningún problema siempre que no se haya ejecutado git add aún.

Realizamos las ediciones necesarias y el o los respectivos commits.

Pasamos a la rama main y realizamos el merge:

Terminal:
$ git checkout main
$ git merge nueva-rama

Opcionalmente eliminamos la rama secundaria:

Terminal:
$ git branch -d nueva-rama

Crear un tag al último commit:

Terminal:
$ git tag nombre-tag

Crear un tag de una versión anotada (-a) con un mensaje (-m):

Terminal:
$ git tag -a v1.0.0 -m "Versión 1.0.0 lista"

Crear un tag que apunta a un commit especificado con un hash:

Terminal:
$ git tag -a v0.1.0 617634b -m "Versión Alfa de nuestra app"

Ver los detalles de un tag:

Terminal:
$ git show v0.1.0

Eliminar un tag:

Terminal:
$ git tag -d nombre-tag

Subir una rama local a remoto

Terminal:
$ git push --set-upstream origin nombre-rama