Programació
Llibreries POO
Resultats d'aprenentatge:
- Escriu programes que manipulin informació seleccionant i utilitzant els tipus avançats de dades facilitats pel llenguatge.
- Gestiona els errors que poden aparèixer en els programes, utilitzant el control d’excepcions facilitat pel llenguatge.
- Desenvolupa interfícies gràfiques d’usuari simples, utilitzant les llibreries de classes adequades.
- Realitza operacions bàsiques d’entrada/sortida d’informació, sobre consola i fitxers, utilitzant les llibreries de classes adequades.
Col.leccions i mapes
Referències
Framework
El framework de col.leccions de Java permet emmagatzemar, obtenir, manipular i comunicar dades agregades. Conté els següents elements:
- Interfícies: són tipus de dades abstractes que representen col·leccions. Les interfícies permeten manipular les col·leccions independentment dels detalls de la seva representació. En llenguatges orientats a objectes, les interfícies formen generalment una jerarquia.
- Implementacions: Són les implementacions concretes de les interfícies de recollida. En essència, són estructures de dades reutilitzables.
- Algorismes: Són els mètodes que realitzen càlculs útils, com cercar i ordenar, en objectes que implementen interfícies de col·lecció. Es diu que els algoritmes són polimorfs: és a dir, es pot utilitzar el mateix mètode en moltes implementacions diferents de la interfície de col·lecció adequada. En essència, els algoritmes són funcionalitats reutilitzables.
A continuació es poden veure les interfícies principals.
- Collection : l’arrel de la jerarquia de col·leccions. Una col·lecció representa un grup d'objectes coneguts com els seus elements. La interfície de col·lecció és el denominador comú que totes les col·leccions implementen i s'utilitza per passar col·leccions i manipular-les quan es desitgi la màxima generalitat. Alguns tipus de col·leccions permeten duplicar elements, i d’altres no. Alguns estan ordenats i d’altres no ordenats. La plataforma Java no proporciona cap implementació directa d'aquesta interfície, però proporciona implementacions de subinterfícies més específiques, com ara Set and List.
- Set : una col·lecció que no pot contenir elements duplicats. Aquesta interfície modela l’abstracció de conjunts matemàtics i s’utilitza per representar conjunts, com ara les cartes que contenen una mà de pòquer, els cursos que configuren el programa d’un estudiant o els processos que s’executen en una màquina. Té una versió ordenada, SortedSet (per valor ascendent).
- List : una col·lecció ordenada (de vegades anomenada seqüència). Les llistes poden contenir elements duplicats. L’usuari d’una llista generalment té un control precís sobre on s’insereix cada element a la llista i pot accedir a elements mitjançant l’índex d’enters (posició). Si heu utilitzat Vector, coneixeu el sabor general de la llista. Consulteu també la secció La interfície de llista.
- Queue : una col·lecció usada per contenir diversos elements abans del processament. A més de les operacions bàsiques de recollida, una cua proporciona operacions addicionals d'inserció, extracció i inspecció. Les cues normalment, però no necessàriament, ordenen elements de manera FIFO (first-in, first-out).
- Deque : és una cua de doble final, i per tant permet inserir, extraure i inspeccionar elements als dos punts. Deques es pot utilitzar tant com FIFO (primer ingrés, primer sortida) com LIFO (darrera entrada, primer sortida).
- Map : un objecte que assigna mapes de valors. Un mapa no pot contenir claus duplicades; cada tecla pot associar com a màxim un valor. Si heu utilitzat Hashtable, ja coneixeu els fonaments bàsics de Map. Consulteu també la secció La interfície del mapa. Té una versió ordenada, SortedMap (per clau ascendent).
Les col·leccions utilitzen el concepte de genèrics de Java. Bàsicament, permet definir el tipus de l'element de les col·leccions com un paràmetre. Per exemple, per a la collecció de tipus List, la definició a la documentació de Java és:
Interface List<E>
Això significa que List és una llista d'elements (E) de tipus parametritzable. Per tant, podem utilitzar List per qualsevol classe (no tipus primitiu).
Genèrics
- Mètodes genèrics
- Paràmetres de tipus delimitat
- Classes genèriques
- Implementacions genèriques
- Comodins
Els genèrics permeten que els tipus (classes i interfícies) siguin paràmetres a l’hora de definir classes, interfícies i mètodes. Un cop que s'instancien, els paràmetres són substituïts pels tipus reals.
Beneficis:
- Controls de tipus més forts a la compilació.
Un compilador Java aplica una verificació de tipus forta al codi genèric i emet errors si el codi viola la seguretat del tipus. La correcció d’errors en temps de compilació és més fàcil que arreglar errors d’execució, que poden ser difícils de trobar.
- Eliminació de casts. El següent codi requereix tipus:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
Quan es reescriu amb genèrics, no el requereix:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- Permet als programadors implementar algoritmes genèrics.
Mitjançant l’ús de genèrics, els programadors poden implementar algoritmes genèrics que treballin en col·leccions de diferents tipus, es poden personalitzar i són de tipus segur i més fàcil de llegir.
Des de Java 7 podem estalviar-nos la definició del paràmetre de tipus del constructor, ja que Java l'infereix:
List<String> list = new ArrayList<>();
Mètodes genèrics
- Totes les declaracions genèriques del mètode tenen una secció de paràmetre tipus delimitada per claudàtors d'angle (<i>) que precedeix el tipus de retorn del mètode (
en el següent exemple). - Cada secció de paràmetres de tipus conté un o més paràmetres de tipus separats per comes. Un paràmetre tipus, també conegut com a variable de tipus, és un identificador que especifica un nom de tipus genèric.
- Els paràmetres de tipus es poden utilitzar per declarar el tipus de devolució i actuar com a marcadors per als tipus d’arguments passats al mètode genèric, que es coneixen com a arguments de tipus reals.
- El cos d'un mètode genèric es declara com el de qualsevol altre mètode. Tingueu en compte que els paràmetres de tipus només poden representar tipus de referència, no tipus primitius (com int, double i char).
public <E> void printArray(E[] inputArray) {
// Display array elements
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
Paràmetres de tipus delimitat
Hi pot haver moments en què voldreu restringir els tipus de tipus que es permeten passar a un tipus de paràmetre. Per exemple, un mètode que opera sobre números només pot voler acceptar instàncies de Number o de les seves subclasses.
Per declarar un paràmetre de tipus delimitat, enumereu el nom del paràmetre del tipus, seguit de la paraula clau extends
, seguit tipus delimitant.
public <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume x is initially the largest
if (y.compareTo(max) > 0) {
max = y; // y is the largest so far
}
if (z.compareTo(max) > 0) {
max = z; // z is the largest now
}
return max; // returns the largest object
}
Es poden tenir múltiples tipus delimitats:
<T extends Type1 & Type2>
Classes genèriques
Una declaració de classe genèrica sembla una declaració de classe no genèrica, tret que el nom de classe sigui seguit per una secció de paràmetre tipus.
Com en els mètodes genèrics, la secció de paràmetres de tipus d'una classe genèrica pot tenir un o més paràmetres de tipus separats per comes. Aquestes classes es coneixen com a classes parametrizades o tipus parametrizats perquè accepten un o més paràmetres.
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Implementacions genèriques
Les interfícies genèriques es poden implementar bàsicament de dues formes: de forma genèrica o no, utilitzant un tipus específic.
Suposem que tenim la següent interfície genèrica:
interface Container<T> {
T getValue();
}
Una implementació genèrica manté el paràmetre genèric:
public class ContainerImpl<T> implements Container<T> {
private T t;
public ContainerImpl(T t) {
this.t = t;
}
@Override
public T getValue() {
return t;
}
}
Una implementació no genèrica utilitza un tipus específic a la interfície:
public class LongContainerImpl implements Container<Long> {
private Long l;
public LongContainerImpl(Long l) {
this.l = l;
}
@Override
public Long getValue() {
return l;
}
}
Així es podrien utilitzar aquestes dues implementacions:
Container<String> strContainer = new ContainerImpl<>("test");
System.out.println(strContainer.getValue().toUpperCase());
Container<Long> longContainer = new LongContainerImpl(12L);
System.out.println(longContainer.getValue() * 2);
Comodins
El comodí (?) permet referir-se a un tipus desconegut. Habitualment s'utilitza com a tipus d'un paràmetre, camp o variable local. Tenim diferents tipus:
- comodins delimitats per dalt:
? extends Foo
. El paràmetre permet Foo o qualsevol subtipus de Foo. Utilitzat si és un paràmetre només d'entrada d'un mètode.
public static void process(List<? extends Foo> list) {
for (Foo elem: list) {
// ...
}
}
- comodins no delimitats:
?
. El paràmetre permet qualsevol tipus. - comodins delimitats per baix:
? super Foo
. Permet Foo o qualsevol supertipus de Foo. Utilitzat si és un paràmetre només de sortida d'un mètode.
Ús de col.leccions
Tota Collection és un Iterable, la qual cosa serveix per iterar qualsevol List, Set o Queue.
Per iterar tenim el mètode iterator():
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
Integer nextInt = iterator.next();
}
També es pot utilitzar el format for-each loop:
for (Integer nextInt: list) {
// ...
}
Hi ha dos mètodes d'Object que utilitzem en relació a les col·leccions, i que sovint cal sobreescriure:
public int hashCode()
: retorna un sencer diferent per a cada objecte. Siequals()
retornatrue
, han de retornar el mateix sencer. S'utilitza per a inserir i cercar a col·leccions que utilitzen taules hash.public boolean equals(Object obj)
: retornatrue
si els dos objectes es consideren iguals. Si es retornatrue
, cal que també hashCode() retorni el mateix sencer.Object
té aquesta implementació per defecte:this == obj
, que significa que són el mateix objecte. Però sovint volem retornartrue
encara que no es tracti del mateix objecte. Per exemple,Integer
retornatrue
si els dos objectes contenen el mateix valorint
.
Implementació típica de equals()
:
public boolean equals(Object o){
if (o == null)
return false;
if (!(o instanceof Treballador))
return false;
Treballador altre = (Treballador) o;
return this.treballadorId == altre.treballadorId;
}
Implementació típica de hashcode()
:
public int hashCode(){
return (int) treballadorId;
}
Implementació alternativa de hashcode()
utilitzant Objects
:
public int hashCode(){
return Objects.hash(treballadorId); // llista de camps de l'objecte
}
Altres mètodes Collection<E>:
boolean add(E e);
void clear();
boolean contains(Object o);
boolean isEmpty();
boolean remove(Object o);
int size()
El mètode contains()
utilitza el mètode equals()
per veure si existeix l'element. Per tant, depèn de la implementació de cada classe. En el cas de Integer
, la documentació diu:
- The result is
true
if and only if the argument is notnull
and is anInteger
object that contains the sameint
value as this object.
Per tant, són iguals dos objectes Integer que contenen el mateix valor sencer. Object
implementa el mètode equals()
com la igualtat (this == o
). Compte, perquè si es pretén sobreescriure el mètode equals()
, sempre s'ha de sobreescriure també el mètode hashcode()
. Veure Objects.hash()
.
List<E> afegeix operacions per posicions:
void add(int index, E element)
E get(int index)
E set(int index, E element)
E remove(int index)
La implementació més clàsica és la de ArrayList
. LinkedList
podria ser interessant si s'insereixen elements al començament amb freqüència.
Set<E> és una col·lecció que no conté repeticions. O sigui, no hi ha dos elements tals que e1.equals(e2)
. No conté mètodes addicionals respecte Collection
.
Tenim tres implementacions:
HashSet
utilitza elhashCode()
de la clau per a optimitzar l'accés als elements.TreeSet
utilitza una estructura en arbre navegable segons l'ordre dels elements, que han de ser comparables (implementen la interfície Comparable). Es basa enTreeMap
.LinkedHashSet
permet navegar els elements segons l'ordre d'inserció.
Queue<E> és una col·lecció amb el concepte associat de "cap" i "cua": lloc per on es treuen i s'afegeixen els elements:
boolean add(E e)
: afegeix un element a la cua (amb excepció).boolean offer(E e)
: afegeix un element a la cua.E remove():
esborra l'últim element al cap (amb excepció).E poll():
esborra l'últim element al cap.E element():
examina l''últim element al cap (amb excepció).E peek():
examina l'últim element al cap.
Com es veu, hi ha dos mètodes per cada operació (afegir, esborrar, examinar), una amb excepció i una altra sense.
Dues de les possibles implementacions:
LinkedList
: la implementació més habitual.PriorityQueue
: permet que la cua estigui ordenada mitjançant un comparador, en lloc d'utilitzar l'ordre en que s'afegeixen els elements.
Deque<E> és una Collection
i també una Queue<E>
. Permet afegir i treure elements als dos costats de la col.lecció, 'first' i 'last'. També permet implementar una pila (classe deprecada Stack) amb els mètodes:
void push(E e)
: afegeix un element a la pila (cap)E pop()
: treu un element de la pila (cap)E peek()
: examina l'element de la pila (cap)
LinkedList
també implementa Deque
.
Ús de mapes
Els mapes són estructures de dades dinàmiques que contenen correspondències entre parelles de clau i valor.
Map<K, V> té aquestes operacions principals:
int size()
boolean isEmpty()
boolean containsKey(Object)
V put(K, V)
V remove(Object)
void clear()
Set<K> keySet()
Collection<V> values()
Set<Entry<K, V>> entrySet()
El tipus Entry<K, V> és una parella clau/valor immutable amb els mètodes:
K getKey()
V getValue()
Les tres principals implementacions són:
HashMap
utilitza elhashCode()
de la clau per a optimitzar l'accés als elements.TreeMap
permet navegar els elements segons l'ordre natural d'aquests, que han de ser comparables (implementen la interfície Comparable).LinkedHashMap
: permet navegar els elements segons l'ordre d'inserció.
Comparació d'objectes
Comparables
Una classe és comparable si permet que les seves instàncies puguin ser comparades per poder ordenar-les entre sí. Totes les classes embolcall dels tipus primitius ho són (String, Integer, Double...). Les classes comparables implementen la interfície Comparable<T>.
La interfície té només un mètode:
int compareTo(T o)
El valor retornat pot ser:
- negatiu, si aquest objecte és menor que o.
- 0, si els dos són iguals.
- positiu, si aquest objecte és major que o.
Les claus de la implementació TreeMap i els objectes d'un TreeSet han de ser comparables. Això permet poder navegar-los segons el seu ordre.
Comparadors
Els comparadors permeten comparar dos objectes d'un tipus T per poder ordenar-los. Són objectes que implementen la interfície Comparator<T>.
La interfície té només un mètode:
int compare(T o1, T o2)
El valor retornat pot ser:
- negatiu, si o1 és menor que o2.
- 0, si els dos són iguals.
- positiu, si o1 és major que o2.
Alguns algorismes permeten utilitzar comparadors per ordenar col·leccions (veure la secció Algorismes). En general, és millor comparar fent la classe comparable, però si no podem o volem modificar el seu codi, podem crear un comparador.
Algorismes
La majoria d'algorismes operen en llistes (List), i alguns a Collection:
static <T extends Comparable<? super T>> void sort(List<T> list)
: ordena una llista d'elements comparables.static <T> void sort(List<T> list, Comparator<? super T> c)
: ordena una llista d'elements, utilitzant un comparador.static void shuffle(List<?> list)
: desordena una llista- Cinc algorismes més per manipular objectes d'una llista:
reverse, fill, copy, swap
iaddAll
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
: cerca binària a una llista- Sobre collections:
frequency, disjoint, min
imax
Stream API
Java té una API que permet processar seqüències d'objectes mitjançant operacions que es poden afegir a una canonada. Un Stream
es genera a partir de col.leccions, arrays o canals d'E/S, i no és modificable: només podem canviar el resultat mitjançant les operacions de la canonada.
Tenim bàsicament dos tipus d'operacions: les intermèdies i les terminals.
- Operacions intermèdies: permeten afegir operacions addicionals al darrere.
- Operacions terminals: marquen el final del stream i retornen el resultat.
Les operacions s'encadenen en la canonada mitjançant crides successives que utilitzen expressions lambda. En el següent exemple, s'utilitza l'operació terminal forEach()
amb una expressió lambda de tipus Consumer.
List<Integer> list = Arrays.asList(3, 2, 5, 4, 1);
Stream<Integer> stream = list.stream();
Consumer<Integer> consumer = (number) -> { System.out.println(number); };
stream.forEach(consumer);
Operacions intermèdies:
-
map: permet aplicar una
Function
per a canviar el tipus delStream
de T a R<R> Stream<R> map(Function<? super T, ? extends R> mapper)
-
flatMap: permet aplicar una
Function
per a convertir cada T als continguts d'un stream de R<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
-
filter: permet modificar el stream filtrant els elements del
Stream
amb unPredicate
Stream<T> filter(Predicate<? super T> predicate)
-
sorted: permet ordenar els elements amb l'ordre natural (han de ser Comparable)
Stream<T> sorted()
Operacions terminals:
-
collect: permet reduir els elements amb un
Collector<T, A, R>
: T és l'entrada, A l'acumulació i R el tipus resultat. Tenim collectors a la classeCollectors
.<R,A> R collect(Collector<? super T,A,R> collector)
-
forEach: permet executar una acció per cada element.
void forEach(Consumer<? super T> action)
-
reduce: permet reduir els elements d'aquest stream utilitzant un
BinaryOperator
que permet ferT apply(T, T)
.Optional<T> reduce(BinaryOperator<T> accumulator)
Exemples:
// crear llista de sencers
List<Integer> number = Arrays.asList(2, 3, 4, 5);
// map
List<Integer> square = number.stream()
.map(x -> x * x)
.collect(Collectors.toList());
System.out.println(square);
// flatMap
List<Integer> square2 = number.stream()
.flatMap(x -> Stream.of(x, x*2))
.collect(Collectors.toList());
System.out.println(square2);
// crear llista de strings
List<String> names = Arrays.asList("Reflection", "Collection", "Stream");
// filter
List<String> result = names.stream()
.filter(s -> s.startsWith("S"))
.collect(Collectors.toList());
System.out.println(result);
// sorted
List<String> show = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(show);
// crear llista de sencers
List<Integer> numbers = Arrays.asList(2, 3, 4, 5, 2);
// collect retorna un Set
Set<Integer> squareSet = numbers.stream()
.map(x -> x * x)
.collect(Collectors.toSet());
System.out.println(squareSet);
// forEach
number.stream()
.map(x -> x * x)
.forEach(y -> System.out.println(y));
// reduce
int even = number.stream()
.filter(x -> x % 2 == 0)
.reduce(0, (ans, i) -> ans + i);
System.out.println(even);
Expressions regulars
Les expressions regulars ens permeten trobar patrons dins de cadenes de text. Podem utilitzar-les per validar dades, fer cerques i substituir-les. Estan implantades a molts gestors de bases de dades.
A Java, tenim un mètode de Pattern que permet comprovar si una cadena compleix un patró, retornant true
en cas positiu. L'ús més general es fa utilitzant les classes Pattern i Matcher.
// regex: 0 a N lletres a i una b
boolean b = Pattern.matches("a*b", "aaaaab");
// alternativament:
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
El funcionament de les expressions regulars a Java s'explica a la documentació de la classe Pattern.
Una de les funcionalitats més interessants de les expressions regulars és la captura de grups. A la secció d'una expressió podem afegir parèntesi, i això voldrà dir que volem obtenir aquell grup de forma individual.
Pattern p = Pattern.compile("(a*)(b*)c");
Matcher m = p.matcher("aaabbc");
boolean b = m.matches(); // true
String group1 = m.group(1); // aaa
String group2 = m.group(2); // bb
Excepcions
Una excepció és un esdeveniment que passa durant l'execució d'un programari, i que trenca el flux normal d'execució d'instruccions.
Quan hi ha una excepció, el mètode on passa crea un objecte (excepció) i la llença a l'entorn d'execució (throws). Aquest objecte indica què ha passat i quin era l'estat quan ha passat. L'objecte de l'excepció inclou la llista de mètodes que s'havia cridat quan passa l'excepció, o call stack.
L'entorn d'execució busca un bloc de codi que pugui gestionar l'excepció. Aquesta cerca es fa des del mètode on es produeix l'excepció i en ordre invers de crida, fins arribar al mètode main. Quan es troba el codi que gestiona l'excepció (catch), l'entorn d'execució passa l'excepció al codi corresponent. Si no troba cap, el programa acaba.
La gestió de l'excepció és apropiada si el tipus de l'excepció coincideix amb el de la gestió de l'excepció.
Avantatges de les excepcions:
- Permeten separar el codi de gestió d'errors del codi normal.
- Permeten propagar els errors, tenint un punt comú per gestionar errors d'un cert tipus.
- Permet agrupar i diferenciar tipus d'errors, utilitzant gestors més o menys generals.
Referències
Tipus d'excepcions
- Checked: són les excepcions que cal gestionar obligatòriament. Es produeixen per condicions fora de l'abast del programa. Exemple:
FileNotFoundException
. Hi ha dues formes de gestionar-les:- una sentència "try/catch"que gestioni l'excepció
try {
// codi que llença una excepció
} catch (SomeException e) {
// codi que gestiona l'excepció
}
- que el mètode especifiqui que pot llençar aquesta excepció (throws)
public void metode() throws SomeException {
// codi que llença una excepció
}
- Unchecked: són les excepcions que no cal gestionar. Habitualment, reflecteixen errors de lògica.
- Errors (tipus
Error
): són esdeveniments excepcionals (del tipus Error) i habitualment irrecuperables. Exemple:OutOfMemoryError
. - Excepcions de l'entorn d'execució (tipus
RuntimeException
): són excepcionals i associats a l'aplicació. Exemple:NullPointerException
.
- Errors (tipus
Gestió d'excepcions
Throws
Es pot tractar una excepció simplement fent throws:
public void metode() throws FileNotFoundException { ...
Try-catch
Podem gestionar l'excepció rellançant-la:
try {
catch (SomeException e} {
throw new AnotherException("un missatge");
}
O bé fent accions per recuperar-nos:
try {
catch (SomeException e} {
return VALOR_PER_DEFECTE;
}
Finally
Quan volem executar algun codi, passi o no passi l'excepció. Pot servir per alliberar recursos.
try {
// ...
} catch (SomeException e) {
// ...
} finally {
// el codi aqui sempre s'executarà
}
Try-with-resources
Permet treballar amb recursos, i tancar-los sense fer-ho explícitament al codi. Cal que el recurs implementi AutoCloseable.
try (AutoCloseableObject o = new AutoCloseableObject()) {
// ...
} catch (SomeException e) {
// ...
}
Multiples catch
Permet gestionar diverses excepcions. L'ordre és important: si tenim excepcions que comparteixen tipus, cal que estiguin les més específiques abans.
try {
// ...
} catch (SomeEspecificException e) {
// ...
} catch (LessEspecificException e) {
// ...
} catch (AnotherException e) {
// ...
}
Union
Permet gestionar diverses excepcions al mateix bloc.
try {
// ...
} catch (SomeException | SomeOtherException | AnotherException e) {
// ...
}
Excepcions pròpies
Habitualment, tenim diverses signatures per crear un objecte d'excepció. Tot i que els paràmetres són lliures (podem afegir els que ens ajudin a interpretar el nostre error particular), dos sovintegen:
- el missatge d'error, un String.
- una excepció, la causa. Permet enllaçar excepcions amb les seves causes (wrapping).
Per exemple, una excepció checked estenent Exception
:
public class SomeException extends Exception {
public SomeException (String message, Throwable t) {
super(message, t);
}
}
Per exemple, una excepció unchecked estenent RuntimeException
:
public class SomeException extends RuntimeException {
public SomeException (String message, Throwable t) {
super(message, t);
}
}
Llençant excepcions
Checked
Es pot llençar, i cal especificar-ho.
public void metode() throws SomeException {
// ...
throw new SomeException("some text");
}
Unchecked
No cal especificar-la:
public void metode() {
// ...
throw new SomeException("some text");
}
Rethrowing
Tornem a llençar l'excepció gestionada:
try {
// ...
} catch (SomeException e) {
throw e;
}
Wrapping
Fem un embolcall:
try {
// ...
} catch (SomeException e) {
throw new SomeOtherException("una explicació", e);
}
Herència
Quan sobreescribim un mètode, podem fer que la subclasse tingui una signatura menys arriscada, o sigui, sense el throws.
Veure la interpretació de les excepcions.
Indicacions
La tentació d'un programador podria ser utilitzar excepcions unchecked, ja que no cal gestionar-les ni declarar-les als mètodes.
Quan un mètode especifica una excepció a la seva signatura, està demanant a qui el crida què vol fer: si tornar a llençar o bé gestionar l'excepció. Les excepcions unchecked són el resultat d'esdeveniments no recuperables, i no té sentit que hagin de ser declarades (tot i que seria possible) per tot arreu, ja que el codi no seria clar.
La regla seria: si una crida pot recuperar-se d'una excepció, fes que sigui checked. Si no pot fer res, fes que sigui unchecked.
Alguns anti-patrons típics:
- Empassar-se excepcions
- Fer return a un finally
- Fer throw a un finally
- Utilitzar throw com a goto
Logging
El registre en logs (logging) permet organitzar d'una forma més óptima els missatges generats en temps d'execució per un programari, i és una alternativa millor que els System.out.println
. Té els següents avantatges:
- Permet assignar una prioritat als missatges, i posteriorment filtrar-los. Tant de forma global com per paquets (packages) i classes.
- Permet reencaminar els missatges a diferents destins, com consola, fitxer, etc.
- Permet adaptar el format dels missatges, com per exemple en text, en XML o a base de dades.
Java conté a la seva llibreria base el paquet java.util.logging
. Aquest paquet pot ser utilitzat de forma bàsica de la següent forma:
private static final Logger LOGGER = Logger.getLogger(LaMevaClasse.class.getName());
Aquesta sentència es pot afegir al començament d'una classe, per tal de fer-hi referència posteriorment. Per exemple:
LOGGER.log(Level.INFO, "un missatge");
El primer paràmetre és el nivell de la notificació, i el segon el missatge. El nivell pot variar entre SEVERE (valor més alt), WARNING, INFO, CONFIG, FINE, FINER i FINEST (valor més baix).
A més, aquest mètode permet utilitzar paràmetres per posició amb el codi {0}, {1}... o passant una excepció.
LOGGER.log(Level.FINE, "un missatge amb paràmetre {0}", "1");
LOGGER.log(Level.WARNING, "un missatge amb paràmetres {0} i {1}", new Object[]{"1", 2});
LOGGER.log(Level.SEVERE, "un missatge sense paràmetre i amb excepció", new RuntimeException("una excepció"));
Tot i que el mètode log(String)
és el més estàndard i convenient, també hi ha mètodes de Logger
per a cada nivell que faciliten l'escriptura:
severe(String), warning(String), info(String), config(String), fine(String), finer(String), finest(String)
Configuració
Si tenim un arxiu loggingConfigFile
(tipus File
) amb l'arxiu de configuració, hem de configurar el registre al començament de l'execució del programari mitjançant la classe LogManager:
LogManager.getLogManager().readConfiguration(new FileInputStream(loggingConfigFile));
Aquesta podria ser una configuració mínima amb nivell màxim de FINE que utilitza un formatador, SimpleFormatter per a la consola i un arxiu:
# two handlers defined:
handlers= java.util.logging.ConsoleHandler, java.util.logging.FileHandler
# default root level, overrided for package and for the LoggingTest class:
level = FINE
test.excepcions.level = INFO
test.excepcions.LoggingTest.level = FINEST
# console handler configuration (https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/ConsoleHandler.html)
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# default SimpleFormatter format
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
# file handler configuration (https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/FileHandler.html)
java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern = %h/logging_test_%g.log
java.util.logging.FileHandler.limit = 1000000
java.util.logging.FileHandler.count = 10
El nivell FINE s'utilitza per defecte per tots els missatges. Es podria configurar per classe:
test.LoggingTest.level = FINEST
Interfícies gràfiques
Referències
- JavaFX 11 Javadoc
- GUI Architectures (Martin Fowler)
- The Observer Pattern Using Java 8
- Observer vs Pub-Sub pattern
- MVC Patterns
- Callbacks
Programació orientada a esdeveniments
Bucle d'esdeveniments
La programació d'interfícies d'usuari es fa mitjançant esdeveniments. Aquesta és la seqüència:
- L'usuari interactua amb el GUI
- Es produeix un esdeveniment
- En resposta, una peça de codi s'executa
- S'actualitza l'aparença del GUI
Aquestes operacions es produeixen dins del bucle d'esdeveniments (event loop). Els esdeveniments s'afegeixen a una mena de cua, i es van satisfent o gestionant amb el codi que el programador ha decidit. Aquest bucle és un sol fil, i per tant no es poden realitzar operacions massa llargues, ja que es bloquejaria el GUI i deixaria de respondre.
El codi equivalent seria:
do {
e = getNextEvent();
processEvent(e);
} while (e != quit);
El flux d'un programa amb GUI no està predeterminat: depèn dels esdeveniments que es produeixin. En contrast, les aplicacions que es recolzen en algorismes esperen unes dades d'entrada en un ordre i temps predeterminat.
Patró observador
El patró principal que s'utilitza a les interfícies gràfiques és el de l'observador. En aquest patró intervenen una parella subjecte/observador. El funcionament bàsic és que tenim un subjecte que genera esdeveniments i un o més observadors que els escolten. Això ens permet fer push dels esdeveniments, en lloc de fer polling. O sigui, comunicar-los quan passen, en lloc d'haver de comprovar si han ocorregut cada cert temps.
Un patró germà és el publish-subscribe, on parlem de missatges en lloc d'esdeveniments. Tenim publicadors que generen missatges, i els subscriptors interessats es registren i els reben. Aquest patró també es relaciona amb les cues de missatges, habitualment utilitzades conjuntament.
Implementació de les notificacions
Quan l'esdeveniment succeeix, el subjecte acaba notificant a tots els observadors amb una crida al un mètode anomenat update(...). Aquesta notificació o update pot implementar-se de diverses maneres:
- El mètode
update(...)
dels observadors pot tenir diversos paràmetres per a indicar a l'observador quin esdeveniment s'ha produït. En el nostre exemple, un paràmetre amb l'esdeveniment anomenatEvent
. - El mètode pot dir-se de moltes maneres. Per exemple,
onEvent()
oactionPerformed()
són altres nomenclatures habituals. - L'objecte
Event
pot contenir el subjecte, el tipus d'esdeveniment (si hi ha més d'un) i altres paràmetres addicionals d'ajuda per a l'observador. - Quan el subjecte genera diversos tipus d'esdeveniments, podem implementar-ho de diferents maneres:
- Tenir una sola classe
Event
i indicar el seu tipus en un camp amb, per exemple, unenum
. - Implementar
Event
com a una classe abstracta amb subclasses per a cada tipus d'esdeveniment, on cadascuna emmagatzema informació diversa. Això requerirà l'ús deinstanceof
per a distinguir-los. - Tenir diverses signatures del mètode de notificació, una per tipus esdeveniment. Per exemple,
onEventX()
,onEventY()
, etc. L'avantatge és que cada mètode pot tenir paràmetres diferents per cada tipus d'esdeveniment.
- Tenir una sola classe
- Tots aquests mètodes no han de retornar res (tipus void). Només es vol notificar als observadors, però el subjecte no necessita res d'ells.
Callbacks
El patró observador utilitza múltiples noms per a la parella subjecte/observador. El subjecte també pot anomenar-se observable o event source. L'observador també pot anomenar-se handler, listener o callback. Tot depèn del context.
Els callbacks són el mateix concepte explicat diferent. Un callback és un codi que passem com a paràmetre a un component, i que aquest executarà més endavant, possiblement de forma asíncrona. Les callbacks s'utilitzen quan només hi ha un observador per al subjecte.
Per exemple, a JavaFX, un botó té aquest mètode:
void setOnAction(EventHandler<ActionEvent> value)
El botó és el subjecte i EventHandler
és el callback que s'executarà quan es cliqui. Per la seva banda, EventHandler
és una interfície funcional, és a dir, amb un sol mètode:
void handle(ActionEvent event)
El client (codi que utilitza la llibreria) haurà d'implementar el mètode handle
, que rep ActionEvent
, un objecte amb el source, target i type de l'esdeveniment.
JavaFX
Aquest apartat utilitzarà JavaFX per a la creació d'interfícies gràfiques. Aquesta plataforma substitueix Swing com a llibreria GUI de Java, i permet desenvolupar aplicacions d'escriptori.
Aquests són els aspectes principals dels components gràfics de JavaFX:
- Un programa JavaFX consisteix a una classe que estén la classe abstracta
javafx.application.Application
. - El contenidor de màxim nivell màxim és
javafx.stage.Stage
. Es correspon amb una finestra. - Els components visuals (nodes) estan continguts dins d'un scene:
javafx.scene.Scene
. - Una aplicació pot contenir diverses scenes, però només una es pot mostrar al stage.
- Un scene conté un graf jeràrquic de nodes:
javafx.scene.Node
.
Per tant, per construir la UI cal:
- Preparar un graf de scene.
- Construir un scene amb el node arrel del graf.
- Configurar el stage amb aquest scene.
Tipus de nodes
Els nodes poden ser de tres tipus:
- Arrel: el primer del graf. Pot ser del tipus 2.
- Parent (Branch)
- Group: és un node col.lectiu que renderitza tots els seus fills en ordre.
- Region: base per als controls UI, com Chart, Pane i Control.
- WebView: gestiona l'engine web.
- Leaf: no conté nodes fill.
Els nodes (components visuals) inclouen:
- Figures geomètriques (
javafx.scene.shape
): Circle, Rectangle, Polygon, etc. - Controls (
javafx.scene.control
): Button, Checkbox, Choice Box, Text Area, etc. - Contenidors (
javafx.scene.layout
): Border Pane, Grid Pane, Flow Pane, etc. - Elements media (Audio, Video, Image)
Cada tipus de node té mètodes que permeten modificar el seu aspecte o el seu contingut, habitualment són getters i setters.
Per exemple: un Label és un node amb un text, i té dos mètodes per accedir i canviar el contingut: setText i getText
.
També hi ha la possibilitat de treballar directament amb un canvas, dibuixant en ell. És la classe Canvas. Aquí hi ha una explicació de com funciona.
Aplicació
Ara veure'm un exemple mínim d'aplicació. Tenim els següents components gràfics:
- Stage: la finestra principal
- Scene: el contenidor del graf d'elements gràfics
- Graf scene: la jerarquia d'elements gràfics, en aquest exemple: Label.
public class JavaWorldApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// creació del stage, scene i scene graph
primaryStage.setTitle("Hello world App");
Label label = new Label("Hello World!");
Scene scene = new Scene(label, 400, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String args[]){
launch(args);
}
}
Scene Graph
Tenim bàsicament dos tipus de grafs de nodes: Group i Region.
Group
Group root = new Group();
ObservableList list = root.getChildren();
list.add(nodeObject1);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
Region
StackPane pane = new StackPane();
ObservableList list = pane.getChildren();
list.add(nodeObject1);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
Layouts
FXML
A l'hora de crear elements gràfics tenim dues opcions: crear-los programàticament o bé amb un arxiu de tipus XML anomenat FXML. El format FXML facilita el dibuix mitjançant eines de disseny com el Scene Builder. A més, permet associar el codi XML amb el codi Java:
- Cal definir un controlador, un objecte Java que serà el nexe de comunicació del món XML i el món Java. Aquest ha d'implementar la interfície javafx.fxml.Initializable.
- Defineix associacions entre objectes al FXML (propietat
fx:id
dels elements) i objectes Java del controlador. - Defineix associacions entre accions al FXML (propietat
onAction
dels elements) i mètodes Java del controlador.
Per a carregar un arxiu FXML cal fer les següents operacions:
FXMLLoader loader = new FXMLLoader();
loader.setController(controlador);
loader.setLocation(getClass().getResource("/cami/arxiu.fxml"));
Parent parent = loader.load();
Scene scene = new Scene(parent);
El camí pot ser absolut (utilitzant la jerarquia de paquets) o bé relatiu al paquet actual, sense utilitzar camí.
Les associacions al FXML es poden fer utilitzant un ID i amb una action:
<Label fx:id="inputLabel"> ... </Label>
...
<Button ... onAction="#onButtonClick" ... />
Aquest codi es correspondrà amb el següent al controlador:
@FXML
private Label inputLabel;
...
@FXML
private void onButtonClick(ActionEvent event) {
...
}
Amb aquest codi podem accedir a l'etiqueta definida al XML mitjançant l'objecte inputLabel
, i cada cop que es cliqui al botó es cridarà al mètode onButtonClick
.
Múltiples finestres
Una stage equival a una finestra.
Podem canviar el contingut d'una finestra modificant el graf de scenes. Això es pot fer amb el mètode:
scene.setRoot(Parent node)
Podem crear finestres modals de tres tipus:
- Alert
- TextInputDialog
- ChoiceDialog
El mètode start(Stage primaryStage) d'una aplicació permet establir la finestra principal, però es podrien crear noves, modals o no. Per fer-ho, crear una stage, i utilitzar els mètodes:
stage.initOwner(Window w)
stage.initModality(Modality m)
Modality pot tenir tres valors:
Modality.NONE
: un stage que no bloqueja cap altra finestra.Modality.WINDOW_MODAL
: un stage que impedeix que els esdeveniments d’entrada es lliurin a totes les finestres des del seu pare fins a l’arrel. La seva arrel és la finestra més avantpassada sense owner.Modality.APPLICATION_MODAL
: un stage que impedeix que els esdeveniments d'entrada es lliurin a totes les finestres des de la mateixa aplicació, excepte els de la seva jerarquia de fills.
Gestió d'esdeveniments
- Processament d'esdeveniments
- Classes anònimes i expressions Lambda
- Mètodes per afegir gestors i filtres
Els esdeveniments notifiquen a l'aplicació de les accions de l'usuari. Els esdeveniments són subclasses d'Event
. Per exemple, MouseEvent
, KeyEvent
, DragEvent
o WindowEvent
.
Partim d'un exemple: un clic del ratolí a un botó. Llavors, un esdeveniment es compon de:
- Destí: el node on succeïx l'esdeveniment. Pot ser una finestra, una escena o un node. En l'exemple, el botó.
- Origen: el lloc on es genera l'esdeveniment. En l'exemple, el ratolí.
- Tipus: el tipus. En l'exemple, clicar el ratolí.
Processament d'esdeveniments
El processament de l'esdeveniment és el següent:
- Selecció del destí:
- Si és un esdeveniment de tecles (keys), l'element que tingui el focus.
- Si és un esdeveniment de mouse, l'element a sota. Si hi ha més d'un, el que estigui a sobre.
- Construcció de l'encaminament: en funció de la jerarquia dels nodes. És el camí des del stage fins arribar al node destí.
- Captura (camí des del stage fins al destí). Aquí no es criden els gestors, però sí els filtres, que poden consumir l'esdeveniment amb
event.consume()
i finalitzar la captura. - Retorn (bombolla): pel camí de tornada cap al stage. Aquí es criden els gestors. Si el gestor d'un node no consumeix l'esdeveniment, un gestor del node pare pot fer-ho, permetent gestors comuns per diversos nodes fill.
Classes anònimes i expressions Lambda
Exemple de gestió d'un esdeveniment d'un botó (control de tipus Button
):
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Botó clicat!");
}
});
Aquest codi utilitza classes anònimes.
També podem utilitzar expressions Lambda, ja que els gestors d'esdeveniments són interfícies funcionals (un sol mètode abstracte):
buttn.setOnAction(
event -> System.out.println("Botó clicat!")
);
Mètodes per afegir gestors i filtres
Els filtres permeten gestionar el processament de l'esdeveniment i consumir-lo, si cal.
<T extends Event> void addEventFilter(
EventType<T> eventType, EventHandler<? super T> eventFilter)
<T extends Event> void removeEventFilter(
EventType<T> eventType, EventHandler<? super T> eventFilter)
Els gestors (handlers) permeten a les aplicacions prendre accions en funció del seu tipus, origen i destí.
<T extends Event> void addEventHandler(
EventType<T> eventType, EventHandler<? super T> eventHandler)
<T extends Event> void removeEventHandler(
EventType<T> eventType, EventHandler<? super T> eventHandler)
Per als gestors tenim els mètodes generals que hem vist i els mètodes de conveniència .setXXX()
que faciliten escriure el codi sense haver d'indicar el tipus d'esdeveniment. Tots els setters treballen amb un sol handler, mentre que l'add/remove permet afegir diversos handlers al mateix esdeveniment.
button.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseHandler);
button.setEventHandler(MouseEvent.MOUSE_CLICKED, mouseHandler);
button.setOnMouseClicked(mouseHandler);
button.setOnAction(actionHandler);
Aquests són alguns dels mètodes de conveniència disponibles:
- General:
setOnAction
- Ratolí:
setOnMouseClicked, setOnMouseEntered, setOnMouseExited, setOnMousePressed
- Teclat:
setOnKeyTyped, setOnKeyPressed, setOnKeyReleased
En general, setOnAction
funciona per tots els controls. Hi ha casos especials, com per exemple si volem atendre el canvi de qualsevol contingut d'un TextField. Es pot utilitzar:
TextField.textProperty().addListener(ChangeListener listener)
I per escoltar un índex numèric sobre un ChoiceBox
:
ChoiceBox.getSelectionModel().selectedIndexProperty().addListener(ChangeListener listener)
Pots veure la llista de controls i com utilitzar-los.
Patró de disseny UI
Un patró de disseny associat típicament al desenvolupament d'interfícies d'usuari (UI) és el model-vista-controlador (MVC). S'utilitza habitualment a l'entorn web, i la seva implementació pot variar. El següent diagrama és una opció.
Una particularització d'aquest patró és el model-vista-presentador (MVP), on el presentador és un controlador que fa d'intermediari entre la vista (passiva) i el model. Està més associat amb aplicacions natives com, per exemple, JavaFX.
Aquestes són les responsabilitats de cada component del patró:
- Vista: genera la part visual de l'aplicació. Envia esdeveniments cap al presentador, i rep peticions del presentador per actualitzar-se.
- Presentador: s'encarrega de mitjançar entre la vista i el model. No conté cap codi associat a la UI.
- Model: part del patró que s'encarrega d'accedir a funcionalitats o dades a una llibreria independent, que no té cap relació amb la presentació visual.
Encapsulació al patró MVP
En aquest esquema, és important encapsular correctament. Si es fa bé, tant la vista com el model serien substituïbles. Això només es pot aconseguir si les tres parts es relacionen mitjançant abstraccions d'un contracte, les quals poden ser implementades mitjançant una interfície Java.
Si fem bé l'encapsulació podem testar tant el presentador com el model. L'aproximació és utilitzar una vista passiva: la vista mai es comunica amb el model, i és el presentador qui gestiona els seus esdeveniments i l'actualitza. Per fer el testing general podem utilitzar un doble de proves de la vista.
Així es defineixen i relacionen les parts:
- La vista envia esdeveniments al seu únic observador, el presentador. Té mètodes que permeten al presentador actualitzar-la, i és agnòstic d'aquest. Només la vista conté classes de la llibreria visual (JavaFX).
- El presentador té una instància de la vista (per enviar-li actualitzacions de la part visual) i una instància del model (per enviar comandes o consultes).
- El model envia esdeveniments al presentador, el seu observador. El model és agnòstic respecte del funcionament del presentador i tampoc coneix cap aspecte visual.
A continuació es mostra una plantilla d'interfícies per a aquest patró.
interface View {
void setListener(ViewListener l); // permet registrar el presenter
// cal afegir comandes des del presentador
}
interface ViewListener {
// cal afegir mètodes que escolten esdeveniments de la view
}
interface Presenter extends ViewListener, ModelListener {
void start(); // mètode d'inici de l'aplicació
void stop(); // mètode de fi de l'aplicació
}
interface Model {
void setListener(ModelListener l); // permet registrar el presenter
// cal afegir els comandes i queries del presenter
}
interface ModelListener {
// cal afegir un mètode per cada esdeveniment generat pel model
}
Concurrència
Les aplicacions GUI (interfície gràfica d'usuari) de Java (inclosa JavaFX) són inherentment multifil. Diversos fils realitzen tasques diferents per mantenir la interfície d'usuari en sincronització amb les accions de l'usuari. JavaFX utilitza un únic fil, anomenat JavaFX Application Thread, per processar tots els esdeveniments de la interfície d'usuari. Els nodes que representen la interfície d'usuari d'una gràfica d'escena no són segurs. El disseny de nodes que no són segurs per a fils presenta avantatges i inconvenients. Són més ràpids, ja que no hi ha cap sincronització. L’inconvenient és que s’han d’accedir des d’un mateix fil per evitar estar en un estat il·legal. JavaFX posa una restricció a la qual s’ha d’accedir a un gràfic d’escena en directe des d’un únic fil, el fil d’aplicacions JavaFX. Aquesta restricció imposa indirectament una altra restricció que un esdeveniment d’UI no ha de processar una tasca de llarga durada, ja que farà que l’aplicació no respongui.
Si un altre fil vol modificar la GUI, cal que utilitzi la següent construcció per afegir la tasca a la cua d'esdeveniments:
Platform.runLater(new Runnable() {
@Override
public void run() {
// acció que es vol realitzar
}
});
Binding de propietats
Una propietat és un atribut accesible públicament i que afecta el seu estat i/o comportament. Les propietats són observables: poden notificar a observadors de canvis. Poden ser només lectura, només escriptura o lectura i escriptura.
El binding de les dades, en aquest context, es refereix a la relació entre variables d'un programa per tal de mantenir-se sincronitzades. A les GUI ens permet mantenir sincronitzats elements de la capa de model amb els elements GUI corresponents. Això s'aconsegueix gràcies a la implementació del patró observador.
Tipus de binding:
- Eager (ansiós) o lazy (mandrós): si el valor de variable es recalcula immediatament quan ho fa la dependència, o només quan es llegeix. Les propietats de JavaFX utilitzen avaluació mandrosa.
- Unidireccional o bidireccional: si només es fa en una direcció la sincronització, o en les dues.
Exemple de binding unidireccional:
IntegerProperty p1 = new SimpleIntegerProperty(1);
IntegerProperty p2 = new SimpleIntegerProperty(2);
p1.bind(p2); // p1 pren el valor de p2
p2.set(3);
int valor1 = p1.get(); // retorna 3
Exemple de binding bidireccional:
IntegerProperty p1 = new SimpleIntegerProperty(1);
IntegerProperty p2 = new SimpleIntegerProperty(2);
p1.bindBidirectional(p2);
p2.set(3);
int valor1 = p1.get(); // retorna 3
p1.set(4);
int valor2 = p2.get(); // retorna 4
Els binding es poden fer a JavaFX utilitzant les propietats associades als elements gràfics. Per exemple:
TextField tf1 = new TextField();
TextField tf2 = new TextField();
tf1.textProperty().bind(tf2.textProperty());
Per associar una etiqueta a una propietat del model de diferents tipus podem utilitzar el mètode asString()
:
Label l = new Label();
IntegerProperty p = new SimpleIntegerProperty(3);
l.textProperty().bind(p.asString());
Persistència POO
Resultats d'aprenentatge:
- Gestiona informació emmagatzemada en bases de dades relacionals mantenint la integritat i la consistència de les dades.
- Gestiona informació emmagatzemada en bases de dades objecte-relacionals mantenint la integritat i la consistència de les dades.
- Utilitza bases de dades orientades a objectes, analitzant-ne les característiques i aplicant tècniques per mantenir la persistència de la informació.
Referències
- JDBC Data Access
- Java Try With Resources (Jenkov)
- Data Access Object (Core J2EE Patterns)
- Patterns of Enterprise Application Architecture (Martin Fowler)
- The Technical Services Layer (tutorial)
- MySQL zip server install
- Enterprise Integration Patterns
- JDBC 4.3 Specification (JSR 221)
- SQLite JDBC Driver
- Appropriate uses for SQLite
- How to choose a database for your application
- MySQL J/Connector Reference
- SQLite Datatypes
- Isolation (Database systems)
- Commons DbUtils
- Persism ORM
Persistència
- Localització de les dades
- Model de dades
- Què, quan i com persistir
- Quin tipus de persistència?
- Base de dades d'aplicació o d'integració
- Model relacional vs NoSQL
- Característiques ACID
- Característiques BASE
Localització de les dades
Una aplicació conté codi i dades. El codi interactua amb les dades que hi ha a la memòria principal, que és volàtil, el tipus de memòria més ràpida que existeix i la més senzilla de llegir i escriure. A POO, les dades són als objectes.
Un problema de la memòria principal és que perdem el seu contingut si l'aplicació finalitza, ja sigui de forma correcta o per un problema. El context necessari per a l'execució d'una aplicació és el seu estat, i cal recuperar-lo cada cop que les executem. Això es fa utilitzant memòria no volàtil basada en un o diversos arxius físics.
Quan parlem de recuperar el context d'una aplicació per a la seva execució, no sempre és possible fer-ho amb totes les dades que es gestionen. Pot ser perquè la mida sigui superior a la de la memòria principal, però també perquè no calgui i sigui millor ser mandrós en la seva càrrega per raó de rendiment.
Model de dades
El disseny del model de dades ha de tenir en compte el millor rendiment possible quan s'utilitza memòria no volàtil, molt més lenta, i facilitar la futura escalabilitat de les nostres dades.
Un mecanisme per a minimitzar temps és la memòria cache: mantenim un nombre limitat d'objectes a la memòria principal per evitar haver-los de carregar des de la memòria no volàtil cada cop, i els invalidem si es modifiquen.
També els SGBD poden proporcionar-nos mecanismes per a processar les dades sense necessitat de carregar-les en memòria, gràcies a llenguatges declaratius com SQL: demanem quines dades necessitem, no com processar-les.
Si parlem de disseny, és convenient crear una capa de persistència que encapsuli el procés. Això ens permetrà substituir-la sense afectar la resta de l'aplicació. Si parlem d'usabilitat, la persistència hauria de ser transparent per a l'usuari.
Què, quan i com persistir
Tenim dos mètodes de persistència: d'estat, el més habitual, i basat en esdeveniments.
Persistència d'estat
Parlant de POO, ens podríem fer la pregunta de si allò que volem persistir és un objecte o una estructura de dades. Els objectes tenen comportament i encapsulen les dades. Les estructures de dades exposen les dades i no tenen comportament. Aquesta distinció ens ajudarà a decidir la nostra estratègia de persistència.
Per definir la persistència, cal repassar els camps de l'objecte i decidir quins necessitarem per tal de tornar a instanciar l'objecte en memòria principal. Alguns camps innecessaris poden ser calculats o tenir una funció temporal. Podem ajudar-nos de l'estudi dels constructors i setters de l'objecte.
A més, ens caldrà fer referències entre els objectes. Quan dissenyem mecanismes ad-hoc, podem utilitzar un identificador d'objecte en el moment de la persistència. En el cas dels SGBD, els objectes es relacionen amb claus foranes.
Tenim diversos àmbits de persistència:
- Context general de l'aplicació: configuracions generals, per usuari, etc.
- Documents, basats en serialització. Persistim en base a un document que pot intercanviar-se.
- Registres, habitualment relacionats i associats a SGBD. No se sol restaurar completament al començament, només quan cal.
Persistència d'esdeveniments
La persistència per esdeveniments (Event Sourcing) és un mecanisme que permet restaurar l'estat d'una aplicació a partir de tots els esdeveniments que s'han produït. Es tracta d'una mena de log on es guarden els esdeveniments per ordre d'ocurrència, i que permet reconstruir l'estat en qualsevol moment del temps.
Quan l'aplicació s'inicia, reconstrueix l'estat actual aplicant tots els esdeveniments en l'ordre en que es val produir. Com que podria haver molts d'esdeveniments, aquest mecanisme se sol combinar amb altres, com per exemple emmagatzemar un estat intermedi (snapshot) i aplicar els esdeveniments a partir d'aquell instant.
Quan i com
Hem de persistir sempre que hi hagi un canvi en alguna dada? La resposta general és que sí. Així evitem perdre dades si el nostre programa falla abans d'haver persistit. Però de vegades no seria òptim, parlant de rendiment, haver de persistir tot l'estat d'una aplicació. Alternativament, es podria endarrerir el moment, o fer-ho periòdicament de forma automàtica. També podem deixar aquesta responsabilitat en mans de l'usuari (File > Save).
La persistència per esdeveniments permet només guardar l'esdeveniment que es produeix en lloc de totes les dades, i també pot ser una solució per millorar el rendiment.
Quin tipus de persistència?
El format i organització dependrà de les nostres necessitats.
Criteris per decidir la forma de persistència:
- És una aplicació monousuari o multiusuari?
- Es comparteix informació a la xarxa amb altres clients o aplicacions?
- Hi ha un volum molt alt de dades?
- Hi ha un esquema estable (ben estructurat)?
- Quins requisits qualitatius tenim: disponibilitat, escalabilitat, latència, rendiment, consistència...?
Si la resposta està a prop d'una aplicació monousuari, sense connexió amb altres clients, i poques dades, segurament podem gestionar-ho mitjançant persistència en fitxer. Caldria, en tot cas, decidir quin és el format d'aquest fitxer, ja que podem utilitzar solucions existents sense necessitar inventar-nos un format a mida. En POO, utilitzem el concepte de serialització d'objectes: generem una representació del graf d'objectes que permet restaurar-los quan l'aplicació torna a executar-se. El format podria ser a mida, utilitzant JSON, un arxiu de preferències clau-valor, etc.
Si la resposta s'assembla a aplicació multiusuari amb informació compartida per la xarxa i grans volums de dades, estem a prop de necessitar un sistema de gestió de base de dades (SGBD). En aquest cas, parlem de mapatge d'objectes relacional (ORM). Aquest procés consisteix a interposar una capa entre la lògica i la persistència de l'aplicació, de tal forma que podem persistir utilitzant el paradigma d'orientació a objecte en lloc del llenguatge SQL. És un procés complex i no exempte de problemes. A Java es pot implementar a mida (JDBC) o bé utilitzar llibreries de mapatge (JPA).
Els SGBD resolen problemes habituals que ens trobem en el desenvolupament d'aplicacions. Entre ells:
- Definició, creació, manteniment i control d'accés a una base de dades.
- Gestió de transaccions i concurrència (segons el model).
- Facilitats per recuperar dades en cas de danys.
- Gestió d'autoritzacions i accés remot.
- Regles de comportament de les dades en funció de la seva estructura.
Base de dades d'aplicació o d'integració
Una base de dades d’integració és una base de dades que actua com a magatzem de dades de diverses aplicacions i, per tant, integra dades d'aquestes aplicacions.
Una base de dades d'aplicació es controla i accedeix des d'una sola aplicació. Per a compartir dades amb altres aplicacions, l'aplicació que controla la base de dades hauria de proporcionar serveis.
La recomanació general és la d'evitar bases de dades d'integració. En general, les bases de dades d’integració comporten problemes greus perquè la base de dades esdevé un punt d’acoblament entre les aplicacions que hi accedeixen. Generalment es tracta d'un acoblament profund que augmenta significativament el risc que suposa canviar aquestes aplicacions i dificulta la seva evolució.
Model relacional vs NoSQL
Les bases de dades actuals responen, moltes d'elles, al model relacional. Aquest model ha triomfat segurament gràcies a l'establiment d'un estàndard per a la gestió de dades, l'SQL. Existeixen alguns altres models que tenen sentit per a certs nínxols, i que cal considerar: bases de dades en graf, multivalor o orientades a objecte. Però han anat perdent ressò en favor de les bases de dades NoSQL. El creixement d'aquestes es veu afavorit pel Big Data i les aplicacions en temps real. Són sistemes habitualment no estructurats (sense esquema), i poden persistir al costat de solucions relacionals en models de persistència poliglota.
Sense sortir del model relacional, solem tenir extensions sense esquema. Per exemple, camps amb contingut JSON. O taules d'atributs que permeten fer JOINs addicionals. Aquestes extensions ens permeten tenir dades sense esquema dins d'un esquema, tot i que aquestes dades no són tan accessibles des de consultes SQL.
El que cal evitar és tenir esquemes implícits al codi d'accés. Només en pocs casos pot tenir sentit no tenir-ne un esquema:
- Camps a mida imposats per l'usuari
- Objectes sense un tipus uniforme (esdeveniments)
- Pot ser més fàcil fer migracions d'esquemes (implícits)
Punts a considerar a l'hora de decidir-se:
- És una BBDD amb o sense esquema (relacionals vs NoSQL)?
- Existeixen relacions, que utilitzarem per navegar aquesta informació?
- La velocitat és un aspecte crític? Les relacionals sacrifiquen la velocitat en favor de la normalització.
- És important tenir escalabilitat? Les NoSQL escalen millor horitzontalment.
- Les relacionals ofereixen les propietats ACID, mentre les NoSQL són BASE.
Característiques ACID
En el context de bases de dades, ACID (acrònim anglès de Atomicity, Consistency, Isolation, Durability) són tot un seguit de propietats que ha de complir tot sistema de gestió de bases de dades per tal de garantir que les transaccions (operacions sobre les dades) siguin fiables.
Concretament, l'acrònim ACID significa:
- Atomicitat: Una transacció o bé finalitza correctament i confirma o bé no deixa cap rastre de la seva execució.
- Consistència: La concurrència de transaccions no pot produir resultats anòmals.
- Aïllament (o Isolament): Cada transacció del sistema s'ha d'executar com si fos l'única que s'executa en aquell moment en el sistema.
- Durabilitat: Si es confirma una transacció, el resultat d'aquesta ha de ser definitiu i no es pot perdre.
Característiques BASE
Les característiques BASE estan associades a les BBDD NoSQL. Es basen en el teorema CAP (Consistency-Availability-Partition Tolerance), que afirma que és molt difícil tenir més de dues d'aquestes propietats alhora.
Són l'acrònim de:
- Basic Availability: la base de dades funciona la majoria del temps.
- Soft-State: no cal tenir consistència a l'escriptura, ni les rèpliques han de ser consistents.
- Eventual consistency: la consistència es pot tenir més tard en el temps (funcionament mandrós).
Fitxers en Java
Podem utilitzar fitxers en un format a mida. Aquests són alguns possibles casos d'ús.
Properties
Les Properties són un format pla per a configuració d'una aplicació en format text. S'utilitzen parelles clau/valor que poden llegir-se (load) i escriure's (store).
JSON
Podem escriure un arxiu de text en format JSON, un format estructurat, i processar-lo utilitzant una de tantes llibreries existents. Per exemple, JSON-java. Poden servir per llegir i persistir l'estat d'una aplicació, quan la quantitat de dades no és massa gran.
YAML
YAML és un format d'entrada estructurat que també té algunes llibreries que permeten llegir-lo, com snakeyaml, però no està tan indicat per persistir.
Arxius d'accés aleatori
Podem utilitzar RandomAccessFile per a crear arxius binaris o de text amb registres de mida fixa. Això ens permet operar amb un índex sense haver de llegir tot l'arxiu com un stream. Hi ha utilitats a Java per exportar i importar tipus primitius utilitzant bytes, com ByteBuffer.
Serialització
La serialització i la deserialització són processos que permeten convertir un objecte (Java) en un format fàcil de persistir i a la inversa.
El mecanisme de la JRE es basa en l'utilització de ObjectInputStream i ObjectOutputStream. Permeten utilitzar un stream per llegir i escriure objectes. Cal que l'objecte implementi Serializable, i es pot fer serializació a mida implementat els mètodes privats writeObject i readObject.
Utilitzar els mecanismes Java de serializació pot fer que la nostra solució estigui tancada a altres llenguatges. Pot no ser un problema, si són arxius que no es compartiran fora.
JSON pot ser una solució de format més intercanviable. Les llibreries existents s'encarreguen de la serialització i deserialització.
Java Database Connectivity (JDBC)
- Controladors (drivers)
- Connexions (connections)
- Sentències (statements)
- Inserció i obtenció d'una clau generada
- java.sql.Date, java.sql.Time, and java.sql.Timestamp
- Excepcions
- Transaccions
- Pool de connexions
JDBC (Java DataBase Connectivity): API estàndard que permet llençar consultes a una BD relacional.
JDBC és el model de persistència bàsic a Java. En funció de la mida i el tipus de projecte és possible que necessitem ajuda per implementar aspectes recurrents al codi:
- Operacions CRUD: si necessitem fer CRUD per moltes taules, seria una feina molt feixuga.
- Generació de queries SQL: ens facilita tenir queries universals ben formades (dialectes).
- Gestió de transaccions: gestió per threads de transaccions i connexions a la BBDD.
- Control de concurrència (optimista, pessimista): mecanismes amb versions/timestamps o amb bloqueig de files.
Els paquets java.sql y javax.sql formen part de Java SE i contenen un bon nombre d’interfícies i algunes classes concretes, que conformen l’API de JDBC. Els components principals de JDBC són: els controladors, les connexions, les sentències i els resultats.
Controladors (drivers)
Un controlador JDBC és una col·lecció de classes Java que us permet connectar-vos a una determinada base de dades. Per exemple, MySQL té el seu propi controlador JDBC. Un controlador JDBC implementa moltes de les interfícies JDBC. Quan el codi utilitza un controlador JDBC determinat, en realitat només utilitza les interfícies estàndard JDBC. El controlador JDBC concret que s’utilitza s’amaga darrere de les interfícies JDBC. D’aquesta manera podeu connectar un nou controlador JDBC sense que el vostre codi ho noti.
Els drivers estan disponibles quan la llibreria (jar) corresponent està al classpath. Per exemple, per a MySQL 8.x podem comprovar-ho amb:
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println(name + "Falta llibreria");
}
Connexions (connections)
Una vegada carregat i inicialitzat un controlador JDBC, heu de connectar-vos a la base de dades. Es fa obtenint una connexió a la base de dades mitjançant l’API JDBC i el controlador carregat. Tota comunicació amb la base de dades es fa a través d’una connexió. Una aplicació pot tenir més d’una connexió oberta a una base de dades alhora, però cal estalviar connexions, ja que són cares, i tancar-les sempre.
Per obtenir una connexió, necessitem una URL, que és dependent de la BBDD concreta. Per exemple:
jdbc:mysql://localhost:3306/test
jdbc:sqlite:path/test.db
Podem obtenir una connexió:
Connection connection = DriverManager.getConnection(url, user, password);
// sentències ...
connection.close();
Una manera alternativa, i recomanable, d'obtenir una connexió és utilitzant el try-with-resources, ja que Connection és un AutoCloseable.
try (Connection connection = DriverManager.getConnection(url, user, password)) {
// sentències ...
} catch (SQLException e) {
e.printStackTrace();
}
Sentències (statements)
Una setència és el que utilitzeu per executar consultes i actualitzacions a la base de dades. Podeu utilitzar alguns tipus diferents d’enunciats. Cada declaració correspon a una sola consulta o actualització. Tenim bàsicament dos tipus de sentències en funció de si la sentència SQL té o no paràmetres (comodins amb ?).
Statement
try (Statement st = connection.createStatement()) {
int count = st.executeUpdate(sql1); // INSERT, UPDATE o DELETE
// o bé...
try (ResultSet rs = st.executeQuery(sql2)) { // SELECT
// processament...
}
}
PreparedStatement
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setType1(1, valor1); // 1 a N, on Type pot ser Int, String...
ps.setType2(2, valor2);
int count = ps.executeUpdate(); // INSERT, UPDATE o DELETE
// o bé...
try (ResultSet rs = ps.executeQuery()) { // SELECT
// processament...
}
}
Conjunts de resultats (ResultSets)
Quan es realitza una consulta a la base de dades, s'obté un conjunt de resultats. A continuació, podeu recórrer aquest ResultSet per llegir el resultat de la consulta.
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
Type1 valor1 = rs.getType1(1);
Type2 valor2 = rs.getType2(2);
// o bé...
Type1 valor1 = rs.getType1("nom_columna1");
Type2 valor2 = rs.getType2("nom_columna2");
}
}
Inserció i obtenció d'una clau generada
De vegades es vol obtenir la clau que s'acaba de generar a una columna automàticament. Això es pot definir a MySQL amb AUTO_INCREMENT o bé a PostgreSQL amb SERIAL. Es pot aconseguir en dos pasos:
- utilitzant el paràmetre
Statement.RETURN_GENERATED_KEYS
quan es crea elPreparedStatement
. - Obtenint el
ResultSet
mitjançant PreparedStatement.getGeneratedKeys(). Habitualment només hi haurà una columna, la posició 1.
int key;
String sql = "INSERT INTO taula (valor) VALUES (?)";
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, valor);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
key = rs.getInt(1);
}
}
}
java.sql.Date, java.sql.Time, and java.sql.Timestamp
Aquestes classes estenen la funcionalitat d'altres Java per representar l'equivalent en SQL. Per exemple, java.sql.Date expressa el dia, mes i any. I java.sql.Time representa hora, minuts i segons. Finalment, java.sql.Timestamp representa java.util.Date fins als nanosegons.
Conversions entre java.sql.Date i java.util.Date: java.sql.Date estén (extends) java.util.Date. Per tant: tots els java.sql.Date són també java.util.Date.
En general, als nostres programes sempre es pot utilitzar java.util.Date, la data genèrica a Java.
Però a JDBC s'utilitza java.sql.Date en dos casos:
void PreparedStatement.setDate(int index, Date sdate)
. Per crear un java.sql.Date a partir d'un java.util.Date:- sdate = new java.sql.Date(udate.getTime()). Per exemple:
preparedStatement.setDate(3, new java.sql.Date(tasca.getDataInici().getTime()));
- sdate = java.sql.Date.valueOf(LocalDate.of(yyyy, mm, dd)).
- sdate = new java.sql.Date(udate.getTime()). Per exemple:
Date ResultSet.getDate(...)
retorna unjava.sql.Date
. Però no cal fer res especial: es pot assignar a un java.util.Date.- udate = sdate. Per exemple:
tasca.setDataInici(result.getDate("data_inici"));
- udate = sdate. Per exemple:
Excepcions
Les sentències SQL sobre JDBC poden generar excepcions SQLException.
Aquest tipus d'excepció té mètodes que poden ajudar a entendre el problema que s'ha produït:
getErrorCode()
: codi específic del proveïdorgetSQLState()
: codi SQLState stàndard
Les excepcions poden passar per diferents motius:
- Problemes de comunicació: connectivitat o servidor parat.
- Problemes d'autenticació: credencials incorrectes.
- Errors de sintaxi SQL, associats a la subclasse
SQLSyntaxErrorException
. Són errors de programació. - Errors de violació de constraints, associats a la subclasse
SQLIntegrityConstraintViolationException
. Indiquen que alguna constraint del DDL no es respectaria, com per exemple una Foreign Key, una Unique/Primary Key, etc. Aquest tipus es gestiona habitualment a l'aplicació per a detectar condicions recuperables.
Transaccions
Una transacció és un conjunt d’accions que s’han de dur a terme com una única acció atòmica. O totes es fan, o cap.
Quan es vol implementar una transacció, és important que totes les operacions de lectura i escriptura que es fan sobre la base de dades es facin compartint una única Connection
. Per altra banda, no és correcte compartir una Connection
entre diferents fils.
Inicieu una transacció per aquesta invocació:
connection.setAutoCommit(false);
Ara podeu continuar fent consultes i actualitzacions de taules. Totes aquestes accions formen part de la transacció.
Si alguna acció intentada dins de la transacció falla, haureu de desfer la transacció. Això es fa així:
connection.rollback();
Si totes les accions tenen èxit, hauríeu de confirmar la transacció. Un cop es fa, les accions són permanents a la base de dades i no hi ha marxa enrera. Es fa així:
connection.commit();
Exemple amb try/catch:
try (Connection conn = factory.getConnection()) {
conn.setAutoCommit(false);
try {
// sentències...
conn.commit();
} catch (Exception e) {
conn.rollback();
throw e; // excepcio original
}
} catch (SQLException e) {
throw new RuntimeException("error SQL", e);
}
En el cas que la mateixa Connection
es vulgui utilitzar després sense transacció, caldria fer:
// no creem la connection
conn.setAutoCommit(false);
try {
// sentències...
conn.commit();
} catch (Exception e) {
conn.rollback();
throw e; // excepcio original
} finally {
conn.setAutoCommit(true);
}
Pool de connexions
El pool de connexions és un conegut patró d’accés a dades, que té com a objectiu principal reduir les despeses implicades en la realització de connexions de bases de dades i en operacions de bases de dades de lectura / escriptura.
En poques paraules, un pool de connexions és, al nivell més bàsic, una implementació de memòria cau de connexió de bases de dades, que es pot configurar per adaptar-se a requisits específics.
Es poden implementar adhoc, utilitzar llibreries de tercers o bé les de les implementacions del controlador JDBC. Sempre que sigui possible, és millor utilitzar un pool de connexions que fer-ho amb DriverManager.getConnection(...). Un pool ens crea un objecte javax.sql.DataSource, que permet obtenir connexions amb el seu mètode getConnection(). A més, el mètode close() de la connexió no la tanca, per poder reutilitzar-la un altre cop.
DataSource ds = // construir-lo segons la base de dades
Connection conn = ds.getConnection(); // obté una nova connexió
...
conn.close(); // retorna la connexió al pool (no la tanca)
Patrons de disseny
- Model de capes d'una aplicació
- Patrons del model
- Patrons de la base de dades relacional
- Patró DAO (Data Access Object)
- Estratègies DAO
- Control de concurrència
Model de capes d'una aplicació
Les capes d'una aplicació que necessita persistència podrien establir-se de la següent manera:
- Presentació: la part que s'encarrega de la interacció amb l'usuari. En el patró MVC, inclouria la vista i el controlador.
- Domini: lògica de negoci, relacionant les dades amb el seu comportament.
- Dades: comunicació amb altres sistemes que fan tasques necessàries per a la nostra aplicació. Per exemple, la base de dades.
On s'executa cada capa?
- Presentació: per a clients rics, al client. Per a B2C, al servidor (HTML).
- Domini: al servidor, més fàcil manteniment. Al client, si és desconnectat. Si cal dividir-la, cal aïllar les dues parts.
- Dades: al servidor, excepte si és un client desconnectat, llavors cal gestionar sincronitzacions.
Patrons del model
Dos estils d'implementació:
- Senzill: similar al disseny de DB, un objecte de domini per taula. Ús del patró Active record (objecte que embolica una fila d'una taula, encapsula l'accés i afegeix lògica de domini en aquestes dades).
- Ric: disseny diferent de la DB, amb herència, estratègies i altres patrons. Ús del patró Data Mapper (una capa de Mappers que mou les dades entre objectes i una base de dades mantenint-les independents les unes de les altres i del mateix mapper).
Patrons de la base de dades relacional
Cal fer un mapeig entre objectes i el món relacional perquè quan programem bases de dades relacionals, el seu model és diferent del dels objectes en memòria. Aquesta és una de les principals dificultats quan treballem amb els dos mons.
El patró general es diu Gateway: un objecte que encapsula l'accés a un recurs o sistema extern. Hi ha dues possibles implementacions:
- Row data gateway: una instància del gateway per cada registre que retorna una consulta.
- Table data gateway (o DAO): una instància gestiona tots els registres en una taula. Els registres es retornen a Record Sets.
Patró DAO (Data Access Object)
A continuació es pot veure el diagrama de classes del patró DAO i un exemple de seqüència.
Aquest patró utilitza un objecte de transferència, TransferObject
, per intercanviar informació entre el client i la base de dades. Aquest objecte és una estructura de dades sense lògica de processament i habitualment immutable.
Cada objecte DAO realitza operacions sobre una taula: creació, lectura, modificació i esborrat.
Disseny d'un DAO
Aquests són els consells a l'hora de dissenyar un DAO:
- És convenient utilitzar sempre una interfície per definir un DAO, per tal d'aïllar millor el domini de les dades.
- Els mètodes públics, si utilitzen altres privats per a executar l'operació, han de compartir la mateixa
Connection
. - No retornar mai objectes associats a la capa de base de dades, per exemple, evitant fer visible els
ResultSet
o elsSQLException
. - Si una excepció SQL no és recuperable, utilitzeu una
RuntimeException
embolcall d'aquesta. - No utilitzar camps al marge del
DataSource
, ja que un DAO hauria de dissenyar-se sense estat per poder ser thread-safe.
Estratègies DAO
Objecte de transferència
L'objecte de transferència es pot utilitzar com es pot veure al següent exemple.
// suposem que a resultSet hi ha un registre d'una cerca
Persona persona = new Persona(); // transfer object
persona.setId(resultSet.getInt("id"));
persona.setName(resultSet.getString("name"));
...
return persona;
Els objectes de transferència poden implementar-se de diferents maneres, en particular, poden ser mutables o immutables. La preferència general seria que fossin immutables al client que els utilitza, malgrat que això pot significar complicar-los pel que fa a la seva programació.
- Com a l'exemple, amb getters i setters. La més convencional.
- Amb una interfície immutable, encara que la seva implementació sigui mutable. La més correcta.
- Amb camps públics i sense getters ni setters. La més senzilla.
- Alternativament, es pot utilitzar un Map<String, Object>, similar al concepte schemaless. No requereix classes, però es perd la validació en temps de compilació. Relacionat amb els JSON.
Col·lecció d'objectes de transferència
Com ja s'ha comentat, és millor no exposar objectes associats a la capa de dades al client. Per exemple, evitar fer visible els ResultSet. Així encapsulem i evitem dependències i haver de gestionar excepcions de tipus SQLException. En aquesta estratègia el DAO crea una sentència SQL i executa una consulta per obtenir un ResultSet. Llavors el DAO processa el ResultSet per recuperar tantes files de resultats coincidents com ho sol·liciti el client que fa la crida. Per a cada fila, el DAO crea un objecte de transferència i l’afegeix a una col·lecció que es retorna al client.
List<Persona> persones = new ArrayList<>();
while (resultSet.next()) {
Persona persona = new Persona(); // transfer object
persona.setId(resultSet.getInt("id"));
persona.setName(resultSet.getString("name"));
persones.add(to);
}
return persones;
Factoria de DAO
Quan tenim diversos DAO que cal crear per diferents taules, un patró habitual és la factoria de DAO.
public class DAOFactory {
private static DAOFactory instance;
private DAOFactory() {
// init ConnectionFactory
}
public static DAOFactory getInstance() {
if (instance == null)
instance = new DAOFactory();
return instance;
}
public CustomerDAO getCustomerDAO() {
// implementar-ho
}
public AccountDAO getAccountDAO() {
// implementar-ho
}
public OrderDAO getOrderDAO() {
// implementar-ho
}
}
Control de concurrència
Quan tractem de llegir i modificar dades, podríem afrontar alguns dilemes sobre la integritat i la validesa de la informació. Aquests dilemes sorgeixen a causa de les operacions de bases de dades xocant entre elles; per exemple, dues operacions d’escriptura o una operació de lectura i escriptura que col·lideixen.
Les estratègies per resoldre aquesta situació poden ser:
- Bloqueig pessimista: s'espera una col·lisió, i llavors es fa un bloqueig dels recursos implicats. Cap altre client pot accedir-los fins que no es lliurin.
- Bloqueig optimista: la col·lisió és poc probable. Es deixa fer, i quan acaba el processament, es comprova si hi ha hagut un problema.
- Bloqueig massa optimista: no s'esperen col·lisions. Potser és un sistema monousuari.
Les transaccions dels SGBD resolen aquests problemes gràcies a la implementació de les característiques ACID. Primer, totes les sentències individuals que s'executen són atòmiques, és a dir, o bé s'executen o no ho fan, però mai provoquen problemes de consistència. Segon, podem estendre aquesta capacitat per a un conjunt de sentències mitjançant l'ús de transaccions.
Les estratègies de resolució de col·lisions poden ser:
- Rendir-se
- Mostrar el problema, i deixar que decideixi l'usuari
- Barrejar els canvis
- Registrar el problema, i que ho resolgui algú altre més tard
- Ignorar la col·lisió
La implementació de transaccions assegura que no hi ha interferències entre elles. Tenim quatre nivells d'aïllament, que equilibren rendiment i l'exactitud de les dades, de menys a més restrictiu:
- Lectura no confirmada: les transaccions poden veure els canvis realitzats per altres transaccions fins i tot abans que aquests canvis es confirmin. Rarament utilitzat.
- Lectura confirmada: una transacció només veu els canvis realitzats per altres transaccions un cop aquests canvis s'han confirmat. Bon equilibri.
- Lectura repetible: assegura que si una transacció llegeix dades més d'una vegada, obtindrà el mateix resultat cada vegada, fins i tot si altres transaccions estan actualitzant les dades. No obstant això, no evita que altres transaccions afegeixin noves files (lectures fantasmes). Útil a aplicacions crítiques, com a les financeres.
- Serialitzable: el nivell d'aïllament més alt, on les transaccions estan completament aïllades entre elles, com si s'executessin una darrere l'altra en ordre. No importa el rendiment, només l'exactitud.
Tanmateix, les transaccions no sempre són la solució en l'àmbit de les aplicacions. Les lectures/escriptures de dades separades en el temps poden provocar contenció de dades. Per exemple, una transacció no pot esperar que un usuari modifiqui les dades després d'haver-les llegit en un formulari.
Els mecanismes del bloqueig pessimista s'implementen mitjançant ordres que permeten bloquejar registres de la base de dades. Es fa perquè hi ha alta contenció i el cost de bloquejar és menor que el de fer enrere una transacció. Els sistemes de BBDD implementen aquest mecanisme amb locks, que poden bloquejar registres o taules. Ho fan de dues formes, segons hi accedeixin lectors o escriptors:
- Locks compartits: múltiples lectors poden obtenir-los, i cap pot escriure mentre estigui algun actiu.
- Locks exclusius: només un escriptor pot modificar un registre.
En canvi, el bloqueig optimista s'implementa fent que hi hagi un error si hi ha hagut col·lisió, i llavors cal que l'usuari torni a fer l'operació. Pot implementar-se amb un control de timestamps, comptadors o versions d'un registre. La modificació dels registres actualitza aquests valors, i pot utilitzar-se com a condició per fer fallar una transacció. Aquest podria ser una possible transacció:
- Inici: guardar un timestamp/versió que marca l'inici de la transacció
- Fer canvis: llegir i intentar escriure a la base de dades
- Validar: veure si les dades modificades són les marcades inicialment
- Confirmar/Desfer: si no hi ha conflicte, fer els canvis; si hi ha, desfer-los
Serveis i processos
Processos i fils
Resultats d'aprenentatge:
- Desenvolupa aplicacions compostes per diversos processos reconeixent i aplicant principis de programació paral·lela.
- Desenvolupa aplicacions compostes per diversos fils d’execució analitzant i aplicant llibreries específiques del llenguatge de programació.
Referències
- Java Concurrency Terminology
- Java Language Specification, capítol 17: Threads and Locks
- The Java tutorials: Concurrency
- Java Concurrency and Multithreading Tutorial
- Threads (Java in a nutshell)
- How to work with wait(), notify() and notifyAll() in Java?
- Thread Communication using wait/notify
- The evolution of the producer / consumer problem in Java
- Java CompletableFuture Tutorial with Examples
- Concurrency in JavaFX 8
- Liveness (The Java Tutorials, Oracle)
- Liveness (Wikipedia)
- The Deadlock Empire
- Thread Safety (6.005: Software Construction)
- What is Thread Dump and How to Analyze them?
- Concurrency: A Primer
Programació i sistemes reactius:
- Reactive Programming: Why It Matters
- Reactive programming vs Reactive systems
- RxJava Wiki
- RxJava Backpressure
- Reactive Manifesto (Glossary)
- RxJava and Reactive Programming
- Poor Man's Actors in Java
- Designing Reactive Systems (llibre de Hugh McKee)
- Reactive in practice: A complete guide to event-driven systems development in Java
- A reactive Java landscape
- Reactive Programming with Reactor 3
Concurrència
Concurrència i paral·lelisme
La computació concurrent permet que diverses tasques dins d'una aplicació puguin executar-se sense un ordre concret, fora d'una seqüència. O sigui: no cal que una tasca es completi perquè comenci la següent. Aquesta és una propietat de l'algorisme. És a dir, cal que dissenyem la nostra aplicació perquè ho permeti.
La computació concurrent no implica que l'execució es produeix en el mateix instant de temps. La computació paral·lela sí que fa referència a l'execució en el mateix instant de temps, i treu profit de sistemes amb múltiples cores per a accelerar la computació. És una propietat de la màquina.
Per una banda, hi ha situacions on les aplicacions són inherentment concurrents. Per una altra, si no dissenyem concurrentment, les nostres aplicacions no poden aprofitar les arquitectures hardware multi-core de les CPU dels ordinadors, i estarem limitats a la capacitat i rendiment d'un sol core.
Processos i fils
El planificador o scheduler d'un sistema és el mecanisme que permet assignar tasques a treballadors. A una aplicació, una tasca sol traduir-se en un fil o thread i un treballador en un core de CPU. L'assignació fa que el planificador substitueixi una tasca per un altra. D'això se'n diu canvi de context (context switching). És un procés pesat per als processos, amb un context més gran, i lleuger per als fils, amb un context més petit.
La unitat bàsica d'execució d'un sistema operatiu és el procés, que són col·leccions de codi, memòria, dades i altres recursos. Un procés té un entorn independent d'execució, simulant un ordinador. Per exemple, té el seu espai independent de memòria. Solen ser sinònim de programa, o aplicació, encara que pugui ser un conjunt de processos.
Un fil és una seqüència de codi que s'executa dins de l'àmbit del procés, i que pot compartir dades amb altres fils. Un fil és la seqüència mínima d'instruccions que gestiona un scheduler. Un procés pot tenir diversos fils executant-se simultàniament a dins.
Quan desenvolupem una aplicació, les seves dades es troben en dos espais de memòria diferents: el heap i la pila de crides (call stack):
-
Pila de crides (call stack): estructura de dades que guarda les rutines actives d'un fil apilades en frames. El frame es crea quan es fa una crida, i s'esborra quan s'acaba la rutina. Cada frame conté:
- L'adreça de retorn
- Els paràmetres de la rutina
- Les variables locals
-
Heap: espai dinàmic de memòria que s'assignen quan es creen dades i es desassignen quan s'esborren. Habitualment aquí trobem els objectes.
El processos són totalment independents, no comparteixen res a la pila o el heap. Els fils poden compartir dades del heap.
Aplicacions de la concurrència
La programació ens permet implementar concurrència de diverses formes segons el llenguatge i el context del desenvolupament. Aquests són possibles casos d'ús:
- A una UI, fer operacions en un treballador independent que no bloquegi la interfície.
- Implementar alarmes i temporitzadors.
- Implementació d'algorismes paral·lels.
- Implementar tasques de múltiples clients concurrents, accedint a recursos compartits.
Models de concurrència
Una tasca pot caracteritzar-se, segons el seu tipus d'activitat, com:
- Limitada per la CPU: és una tasca que necessita la CPU per fer càlculs intensius.
- Limitada per l'E/S (Entrada/Sortida): és una tasca que habitualment està esperant per una operació d'entrada/sortida, com pot ser llegir o escriure a disc o a la xarxa.
L'assignació de tasques del scheduler pot ser de dos tipus: cooperativa o apropiativa.
- Cooperativa: les tasques gestionen el seu cicle de vida, i decideixen quan abandonen el treballador.
- Apropiativa: el scheduler assigna un time slice per a la tasca, i la treu del treballador quan es compleix.
El principal repte per implementar la concurrència és la correcta coordinació entre tasques i l'accés als recursos compartits de manera segura. Aquests són alguns dels enfocaments disponibles:
- Un sol fil: només tenim un fil que es comparteix entre les tasques.
- Estat compartit (o memòria compartida): dues o més tasques comparteixen un estat que totes poden llegir i escriure, i diversos mecanismes permeten fer-ho de manera segura.
- Pas de missatges: no es comparteix res. En el seu lloc, s'intercanvien missatges.
El següent diagrama mostra el model d'un sol fil, el de memòria compartida i el de pas de missatges.
Un sol fil
Aquesta opció simplifica el disseny de la concurrència, ja que no cal utilitzar mecanismes per a gestionar l'accés simultani a recursos compartits. Té el desavantatge que no es poden paral·lelitzar les tasques, però això només és un problema si estan limitades per CPU.
Un exemple habitual és el del bucle d'esdeveniments de les UI. S'implementa amb una cua que rep els esdeveniments i els gestiona ràpidament, ja que només realitzen operacions asíncrones.
Estat compartit
Les tasques concurrents interaccionen llegint i escrivint objectes compartits i mutables en memòria. És complex, ja que cal implementar mecanismes de bloqueig per coordinar els fils.
Imaginem que els fils A i B utilitzen un mateix codi per a compartir els objectes mutables. Aquest codi, que permet que diversos fils l'accedeixin simultàniament de forma segura, s'anomena "thread-safe".
Hi ha quatre estratègies que veurem: confinament, immutabilitat, tipus thread-safe i sincronització.
Pas de missatges
Les tasques concurrents interaccionen enviant-se missatges entre ells (1:1 o N:1) a través d'un canal de comunicació. Les tasques envien missatges amb objectes immutables, i els missatges entrants de cada tasca es col·loquen en cola per a la seva gestió. Ho poden fer de forma síncrona o asíncrona, en funció de si s'espera o no la resposta.
El pass de missatges es pot implementar en dos contextos: entre fils d'un procés (p.ex. cues i productor/consumidor) o entre processos d'una xarxa (p. ex. amb sòcols).
Estat compartit
El disseny de programari concurrent que comparteix dades es basa en la idea que els fils poden accedir a les mateixes dades, i que aquestes dades poden ser modificades per qualsevol fil.
Un exemple de llenguatge que utilitza aquest model és Java.
Tenim dos reptes a resoldre:
-
L'accés segur a les dades compartides. Quins mecanismes podem utilitzar per assegurar-nos que els fils accedeixen de forma segura a les dades compartides?
-
La coordinació entre els fils. Com podem gestionar l'ordre d'execució dels fils, sincronitzar-los quan calgui i gestionar les seves interaccions?
Accés segur
Si l'accés de tots els fils és només de lectura, no hi ha problema. El problema és quan hi ha accés simultani de lectura i escriptura.
HI ha dues situacions problemàtiques: la interferència de fils i la coherència de dades.
Interferència de fils
La interferència es produeix quan dues operacions, que s'executen en diferents fils, però que actuen sobre les mateixes dades, s'entrellacen. Això vol dir que les dues operacions consten de diversos passos i les seqüències de passos se superposen. Aquest fenomen també s'anomena race condition.
Per exemple: l'operació d'increment d'un comptador consta de llegir el valor actual, incrementar-lo i escriure el nou valor. Si dues operacions d'increment s'executen simultàniament, el resultat final pot ser erroni.
La solució és sincronitzar l'accés a les dades compartides, fent que cada operació s'executi de forma atòmica.
Coherència de dades
El problema és que dues operacions, que s'executen en diferents fils, però que actuen sobre les mateixes dades, no veuen els canvis que s'han fet en les dades. Això es deu a que els fils tenen una còpia local de les dades compartides, i no es veuen els canvis que fan els altres fils.
Per exemple, si un fil canvia una variable booleana de valor i un altre fil llegeix el valor, pot ser que no vegi el canvi. Això es deu a que el compilador pot optimitzar el codi, i no llegir el valor de la variable cada vegada que es fa servir, sinó que el guarda en un registre. Això fa que el fil no vegi els canvis que fan els altres fils.
Estratègies
Les possibles estratègies per a gestionar l'accés simultani a dades compartides són: confinament, immutabilitat, tipus thread-safe i sincronització.
- Confinament. No compartiu la variable entre fils.
- Immutabilitat. Feu que les dades compartides siguin immutables. Tots els camps de la classe han de ser finals.
- Tipus de dades thread-safe. Encapsulem les dades compartides en un tipus de dades existent amb seguretat que realitzi la coordinació.
- Sincronització. Utilitzeu la sincronització per evitar que els fils accedeixin al mateix temps establint zones crítiques de codi: fragments de codi que accedeixen a dades compartides i que s'han d'executar de forma atòmica.
Coordinació
A l'hora de dissenyar el flux del programari concurrent, fes-te aquestes preguntes:
- Cal entendre la solució al problema. Habitualment, parteix de la solució seqüencial, per trobar la concurrent.
- Considera si pot ser paral·lelitzada. Alguns problemes són inherentment seqüencials.
- Pensa en les oportunitats de paral·lelitzar que permeten les dependències entre les dades. Si no hi ha dependències, podem descompondre-les i paral·lelitzar-les.
- Busca els llocs on la solució consumeix més recursos, com a candidats de paral·lelització.
- Descompon en tasques el problema, per veure si aquestes poden ser executades independentment.
Aquests són alguns mecanismes disponibles a diferents llenguatges per a coordinar els fils:
- Crear una o més tasques que aniran executant-se en paral·lel.
- Que un fil esperi que es completi un altre (join).
- Que un fil notifiqui a un altre que ha completat una tasca (notify).
- Que un fil esperi que un altre li notifiqui que ha completat una tasca (wait).
- Que un fil envii un senyal d'interrupció a un altre fil (interrupt).
Vitalitat (liveness) d'un sistema multifil
La vitalitat d'una aplicació (liveness) és la seva capacitat per a executar-se en el temps que toca. Els problemes més habituals que poden desbaratar aquesta vitalitat són:
- El deadlock (interbloqueig): dos o més fils es bloquegen per sempre, esperant l'un per l'altre. Pot passar si dos fils bloquegen recursos que necessiten esperant que estiguin lliures d'altres, que mai ho seran.
- La starvation (inanició): la denegació perpètua dels recursos necessaris per a processar una feina. Un exemple seria l'ús de prioritats, on sempre els fils amb més prioritat són atesos, i els altres mai ho són.
- El livelock és molt semblant al deadlock, però els fils sí que canvien el seu estat, tot i que mai s'arriba a una situació de desbloqueig.
Quan un client fa una petició a un servidor, el servidor ha d'aconseguir l'accés exclusiu als recursos compartits necessaris. L'ús correcte de les zones crítiques permetrà que el sistema tingui una millor vitalitat quan la càrrega de peticions sigui alta.
Java
Java és un llenguatge de programació d'alt nivell orientat a objectes, compilat i interpretat. Els seus programes són compilats a bytecode, que és interpretat per la màquina virtual de Java (JVM). Aquesta màquina virtual és un procés que s'encarrega de gestionar la memòria i els seus fils d'execució.
El call stack de Java pot contenir dades primitives i referències a objectes. Els objectes es guarden al heap, i el Garbage Collector s'encarrega de gestionar la seva memòria.
El llenguatge Java implementa la concurrència fent ús del model d'estat compartit i fils.
Una aplicació Java pot crear processos addicionals utilitzant ProcessBuilder. Aquesta classe permet crear processos de sistema (Process) i executar-los, parar-los, llegir la seva sortida, reencaminar-la, etc. Resumint, permet interactuar amb altres processos de sistema que no siguin de la màquina virtual Java.
Tanmateix, Java basa la seva implementació de concurrència en la utilització de fils d'execució, que serà el que veurem a continuació. Aquesta es pot implementar a dos nivells: l'API de baix nivell basada en fils i els objectes concurrents d'alt nivell.
Fils a Java
L'API de baix nivell de Java es basa en l'objecte Thread, que pot tenir una sèrie d'estats.
- NEW: Un fil que no s'ha iniciat.
- RUNNABLE: Un fil que s'està executant a la JVM. És possible que no tingui la CPU.
- BLOCKED: Un fil que està esperant per entrar a un bloc sincronitzat (esperant un monitor lock).
- WAITING: Un fil que espera indefinidament per un altre (operacions wait, join).
- TIMED_WAITING: Un fil que espera un cert temps per un altre (operacions sleep, wait, join).
- TERMINATED: Un fil que ha acabat. No es pot tornar a executar.
Podem crear un fil de dues maneres:
- Estendre la classe Thread i reescriure el mètode "run" (millor no utilitzar aquest mètode).
- Implementar la interfície Runnable i el seu mètode "run". Llavors, crear un Thread passant aquest objecte al constructor:
new Thread(new MyRunnable())
Un cop tenim el Thread, el podem executar mitjançant el seu mètode start()
, que farà canviar el seu estat de NEW a RUNNABLE.
Per als exemples que veurem a continuació, utilitzarem la següent utility class:
public class Threads {
private static long start = System.currentTimeMillis();
private Threads() {}
public static void log(String message) {
System.out.println(String.format("%6d %-10s %s",
System.currentTimeMillis() - start,
Thread.currentThread().getName(), message));
}
public static void spend(long millis) {
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < millis);
}
public static void rest(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Els seus mètodes poden cridar-se després de fer un import static Threads.*;
.
A continuació, podem veure un exemple de creació d'un fil anomenat "fil" des del fil principal, "main". Mostra l'estat del fil "child" abans i després de la seva execució.
Aquest seria el codi:
public class StatesThread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("spending");
spend(750);
log("resting");
rest(750);
log("ending");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable(), "child");
log(thread.getState().name());
thread.start();
rest(250);
log(thread.getState().name());
rest(875);
log(thread.getState().name());
rest(500);
log(thread.getState().name());
}
}
Aquesta podria ser una sortida per pantalla possible:
0 main NEW
8 child spending
258 main RUNNABLE
758 child resting
1133 main TIMED_WAITING
1508 child ending
1633 main TERMINATED
Aquest exemple no presenta dificultats, ja que no es comparteix cap dada. Tampoc s'utilitza cap mecanisme de sincronització entre els dos fils.
Tècniques de disseny
Hi ha bàsicament quatre tècniques per assegurar-nos que no tindrem problemes accedint a variables en memòria compartida. Són aquestes, per ordre de preferència:
- Confinament. No compartiu objectes entre fils.
- Immutabilitat. Feu que les dades compartides siguin immutables. Tots els camps de la classe han de ser finals.
- Utilitzar tipus de dades thread-safe. Encapsulem les dades compartides en un tipus de dades existent amb seguretat que realitzi la coordinació.
- Per exemple, el paquet java.util.concurrent també conté algunes classes concurrents de mapes, cues, conjunts, llistes i variables atòmiques. Aquestes classes es poden utilitzar i compartir sense por a provocar race conditions.
- Sincronització. Utilitzeu la sincronització per evitar que els fils accedeixin les dades al mateix temps.
- Els objectes monitor són objectes a què només pot accedir un fil alhora. Aquests permeten definir zones crítiques de codi. És el mètode més utilitzat.
- També es pot utilitzar els reentrant locks (lock / unlock).
A continuació veurem un exemple de race condition, el problema més representatiu de l'estat compartit entre fils. Dos fils intenten incrementar un comptador a un objecte compartit. El resultat final no és el que esperem:
public class RaceThread {
static class SharedObject {
int counter;
}
static class MyRunnable implements Runnable {
SharedObject so;
MyRunnable(SharedObject so) {
this.so = so;
}
void increment() {
so.counter ++;
}
@Override
public void run() {
for (int i=0; i<1_000_000; i++) {
increment();
}
}
}
public static void main(String[] args) throws InterruptedException {
SharedObject so = new SharedObject();
Thread t1 = new Thread(new MyRunnable(so));
Thread t2 = new Thread(new MyRunnable(so));
t1.start();
t2.start();
t1.join();
t2.join();
log("counter is " + so.counter);
}
}
En aquest exemple, el mètode increment()
no és thread-safe. El resultat és que el valor de counter
no és el que esperàvem. Això és degut a que el mètode increment()
no és atòmic. Això vol dir que no és una única operació, sinó que es descompon en diverses operacions més petites. És el que es diu una operació composta. El problema és que cada petita operació pot intercalar-se entre diversos fils i generar un problema.
En el nostre cas, el compilador descompon el mètode increment()
en tres operacions anomenades Read-Modify-Write:
- Llegir el valor de
counter
de memòria. - Incrementar el valor llegit.
- Escriure el valor incrementat a memòria.
Aquestes són algunes operacions compostes habituals:
- Read-Modify-Write (l'exemple)
- Check-Then-Act: per exemple, inicialitzar si cal (singleton)
- Put-If-Absent: afegir un element a una col·lecció si no existeix
Per a sincronitzar, necessitem definit el concepte de monitor (lock): un monitor és un objecte que només pot ser accedit per un únic fil al mateix temps. En el nostre problema, el monitor seria l'objecte so
. Cada fil ha d'adquirir el monitor abans d'accedir a la variable counter
. Això es fa amb la paraula clau synchronized.
Una primera solució seria no permetre l'accés directe al camp counter
de l'objecte so
. En aquest cas, el camp counter
seria private
i només es podria accedir a ell a través de mètodes. Aquests mètodes serien sincronitzats:
static class SharedObject {
private int counter;
synchronized void increment() {
counter ++;
}
synchronized int getCounter() {
return counter;
}
}
I canviar el mètode increment
Una segona solució seria fer que dos fils no poguessin accedir a la variable counter
al mateix temps. Això a Java es fa utilitzant un monitor (lock). El mètode increment()
quedaria així:
void increment() {
synchronized (so) {
so.counter ++;
}
}
Quan creem zones crítiques de codi amb els mecanismes de Java podem ordenar els esdeveniments que es produeixen. Les regles d'ordenació s'expliquen amb el concepte de "happen-before" (passa-abans). Resumint, es tracta de que si un esdeveniment A passa abans que un esdeveniment B, aleshores B no pot passar abans que A. Això ens permet assegurar-nos que els esdeveniments es produeixen en l'ordre que volem.
Hi ha una dificultat important a l'hora de dissenyar codi thread-safe, és a dir, que sigui segur davant l'accés de múltiples fils. Es poden preparar proves per al nostre codi que comprovin si, un nombre important de fils executant simultàniament el nostre codi, provoca problemes. Però no sempre és fàcil simular aquesta situació.
Si mirem la documentació de la Java Standard Edition, veurem que de vegades es fa referència a la condició "thread-safe" de les classes.
Per exemple, a la classe java.util.regex.Pattern es diu:
- Instances of this class are immutable and are safe for use by multiple concurrent threads. Instances of the
Matcher
class are not safe for such use.
És important que quan dissenyem el nostre codi siguem conscients de si necessitem que més d'un fil accedeixi. I si és així, dissenyar la classe en conseqüència i documentar-ho.
Join, interrupt i volatile
Join
Un join fa que un fil s'esperi a la finalització d'un altre. El fil que fa el join passa a l'estat WAITING fins que el fil que s'espera acaba. Si el fil que s'espera ja ha acabat, el join no fa res.
public class JoinThread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
spend(1500);
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
thread.join();
log("joined");
}
}
Interrupt
Una interrupció és una indicació a un fil de què ha de parar de fer el que està fent i fer una altra cosa. Els fils tenen un flag que indica si han estat interromputs o no. Hi ha dues formes de comprovar si un fil ha estat interromput:
- Que el fil faci crides freqüents a mètodes que facin throw de InterruptedException. Per exemple, Thread.sleep(). També serveix si la interrupció s'ha produït abans del sleep().
- Que el fil comprovi freqüentment Thread.currentThread().isInterrupted().
Un cop feta la comprovació i detectada la interrupció, el flag d'interrupció del fil es retorna a false
i cal decidir què fer. Normalment, el que farà és acabar la seva execució, però no és obligatori. Si no es fa res, el fil continuarà executant-se.
public class Interrupt1Thread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
log("interrupted!");
}
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
thread.interrupt();
thread.join();
log("joined");
}
}
public class Interrupt2Thread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
while (!Thread.currentThread().isInterrupted());
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
rest(1500);
thread.interrupt();
thread.join();
log("joined");
}
}
Estat compartit
Es pot acabar un fil compartint una variable que un fil modifica i l'altre llegeix. En Java, cal definir la variable com a volatile
, indicant que els canvis fets en un fil siguin visibles en la resta. O bé utilitzar objectes segurs creats per nosaltres (mecanismes de sincronització) o d'una llibreria segura (p. ex. la atomic de Java).
La paraula clau volatile
només s'ha d'utilitzar si un fil escriu i l'altre (o altres) llegeixen. Si diversos fils escriuen i llegeixen, cal gestionar-ho com a zones crítiques.
public class VolatileThread {
static class SharedObject {
boolean done; // volatile keyword needed
}
static class MyRunnable1 implements Runnable {
SharedObject so;
MyRunnable1(SharedObject so) {
this.so = so;
}
@Override
public void run() {
spend(1500);
so.done = true;
}
}
static class MyRunnable2 implements Runnable {
SharedObject so;
MyRunnable2(SharedObject so) {
this.so = so;
}
@Override
public void run() {
boolean done = false;
while (!done) {
done = so.done;
}
}
}
public static void main(String[] args) throws InterruptedException {
SharedObject so = new SharedObject();
Thread t1 = new Thread(new MyRunnable1(so));
Thread t2 = new Thread(new MyRunnable2(so));
t1.start();
t2.start();
t1.join();
log("joined 1 with " + so.done);
t2.join();
log("joined 2 with " + so.done);
}
}
Mecanismes de sincronització
Monitors (intrinsic lock)
A Java, la sincronització es fa mitjançant monitors. Un monitor és un objecte qualsevol que pot tenir un únic fil propietari. Qualsevol fil pot demanar la propietat d'un monitor i a canvi accedir a una zona crítica de codi restringida. Si ja hi ha propietari, cal que s'esperi fins que ho deixi de ser.
Per demanar la propietat d'un monitor i l'accés a la zona crítica de codi, podem utilitzar la paraula reservada "synchronized". En funció d'on ho fem, l'objecte monitor canvia:
- Mètodes d'instància: L'objecte monitor és la instància. Per tant, només un fil per cada instància.
- Mètodes de classe: L'objecte monitor és la classe. Per tant, només un fil per cada classe.
- Blocs de codi: s'ha d'indicar l'objecte monitor dins dels parèntesis. Qualsevol objecte pot ser monitor (p. ex.
new Object()
), tot i que habitualment fem que el monitor sigui el mateix objecte sobre el qual volem exercir control d'accés.
Un exemple de mètodes d'instància:
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
Conseqüències:
- Primer, no és possible que dos fils puguin cridar simultàniament a dos mètodes sincronitzats. Les subseqüents crides se suspenen fins que el primer fil acabi amb l'objecte.
- Segon, quan un mètode sincronitzat acaba, estableix una relació happens-before: les crides subseqüents tindran visibles els canvis fets.
Important: dins d'un bloc sincronitzat, cal fer la feina mínima possible: llegir les dades i si cal, transformar-les.
Un exemple de blocs de codi:
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
Aquest mètode permet tenir un gra més fi: pot haver-hi un fil a la zona de codi de lock1 i un altre a la de lock2.
Wait / Notify (guarded lock)
Imaginem que volem esperar fins que es compleixi una condició:
public void waitForHappiness() {
// Simple method, wastes CPU: never do that!
while (!hapiness) {}
System.out.println("Happiness achived!");
}
Això ho podem fer entre fils mitjançant el mètode clàssic de comunicació wait i notify, que permet:
- Esperar fins que una condició que implica dades compartides sigui certa, i
- notificar a altres fils que les dades compartides han canviat, probablement activant una condició per la que esperen altres fils.
Els mètodes són:
- wait(): quan es crida, el fil actual espera fins que un altre fil cridi notify() o notifyall() sobre aquest monitor.
- notify(): desperta un fil qualsevol de tots els que estiguin esperant a aquest monitor.
- notifyAll(): desperta tots els fils que estiguin esperant a aquest monitor.
Els mètodes wait() i notify s'han de cridar des de dins d'un bloc sincronitzat per a l'objecte monitor.
A més, tal i com es comenta a Object, el mètode wait() ha de ser a dins d'un bucle esperant per una condició:
// in one thread:
synchronized (monitor) {
while (!condition) {
monitor.wait();
}
}
// in the other thread:
synchronized (monitor) {
monitor.notify();
}
En el nostre cas:
synchronized (monitor) {
while (!happiness) {
monitor.wait();
}
}
...
synchronized (monitor) {
happiness = true;
monitor.notify();
}
Funcionament del wait / notify
- El fill 1 obté el monitor i comprova si la condició és true per acabar el loop.
- Com que és false, fa wait() i deixa el monitor.
- El fill 2 obté el monitor, canvia la condició a false i fa notify().
- El fill 1 es desperta i passa a 'ready', demanant pel monitor.
- Quan l'obté, veu que la condició és true i acaba el loop.
Reentrant Lock
L'ús de synchronized és molt senzill i suficient en molts escenaris. Però hi ha una implementació, ReentrantLock, que permet les següents característiques addicionals:
- Intent de bloqueig: permet provar de bloquejar un lock sense haver d'esperar.
- Locks justos: permeten que els fils s'acabin executant en l'ordre en que han demanat el lock.
- Locks condicionals: permeten que un fil esperi fins que una condició es compleixi.
- Locks amb interrupció: permeten que un fil esperi fins que una condició es compleixi, però que es pugui interrompre.
Aquest podria ser el nostre fil per a l'exemple del comptador:
static class MyRunnable implements Runnable {
SharedObject so;
Lock lock;
MyRunnable(SharedObject so, Lock lock) {
this.so = so;
this.lock = lock;
}
void increment() {
try {
lock.lock();
so.counter ++;
} finally {
lock.unlock();
}
}
@Override
public void run() {
for (int i=0; i<1_000_000; i++) {
increment();
}
}
}
Com es pot veure, el mètode increment() fa servir un lock per a sincronitzar l'accés a la variable compartida. Això és el mateix que fer servir un mètode sincronitzat, però amb la diferència que podem fer servir el lock en un bloc de codi que pot llançar una excepció.
Llibreria Java concurrent
La llibreria java.util.concurrent conté classes útils quan fem concurrència:
- Executors: la interfície Executor permet representar un objecte que executa tasques. ExecutorService permet el processament asíncron, gestionant una cua i executant les tasques enviades segons la disponibilitat dels fils.
- Cues: ConcurrentLinkedQueue, BlockingQueue.
- Sincronitzadors: els clàssics semàfors (Semaphore), CountDownLatch.
- Col·leccions concurrents: per exemple,
ConcurrentHashMap
, o els mètodes de CollectionssynchronizedMap()
,synchronizedList()
isynchronizedSet()
. - Variables que permeten operacions atòmiques sense bloqueig al paquet java.util.concurrent.atomic: AtomicBoolean, AtomicInteger, etc.
Sempre és preferible utilitzar aquestes classes que els mètodes de sincronització wait/notify, perquè simplifiquen la programació. De la mateixa manera que és millor utilitzar executors i tasques que fils directament.
Tasques i executors
La majoria d'aplicacions concurrents s'organitzen mitjançant tasques. Una tasca realitza una feina concreta. D'aquesta forma, podem simplificar el disseny i el funcionament.
Veiem una possible solució per a la gestió de connexions a un servidor. Suposem que tenim un mètode, atendrePeticio()
, que atén una petició web.
Execució seqüencial
class ServidorWebUnFil {
public static void main(String[] args) throws IOException {
ServerSocket socol = new ServerSocket(80);
while (true) {
Socket connexio = socol.accept();
atendrePeticio(connexio);
}
}
}
Un fil per cada petició
class ServidorWebUnFilPerPeticio {
public static void main(String[] args) throws IOException {
ServerSocket socol = new ServerSocket(80);
while (true) {
Socket connexio = socol.accept();
Runnable tasca = new Runnable() {
@Override
public void run() {
atendrePeticio(connexio);
}
}
new Thread(tasca).start();
}
}
}
Grup compartit de fils
class ServidorWebExecucioTasques {
private static final int NFILS = 100;
private static final Executor executor = Executors.newFixedThreadPool(NFILS);
public static void main(String[] args) throws IOException {
ServerSocket socol = new ServerSocket(80);
while (true) {
final Socket connexio = socol.accept();
Runnable tasca = new Runnable() {
public void run() {
atendrePeticio(connexio);
}
};
executor.execute(tasca);
}
}
}
En aquesta solució hem introduït la interfície Executor:
public interface Executor {
void execute(Runnable command);
}
És un objecte que permet executar Runnables. Internament, el que fa és executar tasques de forma asíncrona, creant un fil per cada tasca en execució, i retornant el control al fil que crida el seu mètode execute
. Les tasques poden tenir quatre estats:
- Creada
- Enviada
- Iniciada
- Completada
Els Executors es poden crear des de la classe amb mètodes estàtics Executors. Aquesta classe retorna una subclasse de Executor, l'ExecutorService. Aquesta subclasse usa el patró Thread Pool, que reutilitza un nombre màxim de fils entre una sèrie de tasques a una cua.
Un ExecutorService
ha de parar-se sempre amb el mètode shutdown()
, que para tots el fils del pool.
Tasques amb resultats
Algunes tasques retornen resultats. Per implementar-les, podem utilitzar les interfícies Callable i Future:
public interface Callable<V> {
V call() throws Exception;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException, CancellationException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutException;
}
Callable<V>
permet executar la tasca i retornar un valor del tipus V. Per tal de poder executar-la, necessitem un ExecutorService
. En particular, els seus dos mètodes:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
Aquests permeten executar un Runnable / Callable i retornen un Future, que és un objecte que permet obtenir el resultat en diferit mitjançant el mètode get()
(bloqueig) o get(long timeout, TimeUnit unit)
(bloqueig per un temps).
També podem cancel·lar la tasca mitjançant cancel(boolean mayInterruptIfRunning)
: el paràmetre diu si es vol interrompre també si ja ha començat.
Els ExecutorService poden crear-se mitjançant la mateixa classe que hem vist abans, Executors.
A continuació, un exemple de funcionament. Com canvia l'execució si fem Executors.newFixedThreadPool(2)?
public class SimpleCallableTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> f1 = executor.submit(new ToUpperCallable("hello"));
Future<String> f2 = executor.submit(new ToUpperCallable("world"));
try {
long millis = System.currentTimeMillis();
System.out.println("main " + f1.get() + " " + f2.get() +
" in millis: " + (System.currentTimeMillis() - millis));
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
executor.shutdown();
}
private static final class ToUpperCallable implements Callable<String> {
private String word;
public ToUpperCallable(String word) {
this.word = word;
}
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name + " calling for " + word);
Thread.sleep(2500);
String result = word.toUpperCase();
System.out.println(name + " result " + word + " => " + result);
return result;
}
}
}
A Java 7 es va introduir el framework fork/join.
A Java 8 es va introduir el CompletableFuture, que permet combinar futurs i gestionar millor els errors que es produeixen. Un exemple és l'ús del mètode complete per a completar un futur, en un altre fil:
CompletableFuture<String> completableFuture = new CompletableFuture<>();
//...
String resultat = completableFuture.get();
// mentre en un altre fil...
completableFuture.complete("Hola, món!);
O bé, la possibilitat d'executar directament amb supplyAsync:
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "Hola, món!";
}
};
Future<String> future = CompletableFuture.supplyAsync(supplier, executor); // executor és opcional
System.out.println(future.get());
Pas de missatges
- Model de programació síncron i asíncron
- Comunicació asíncrona
- Gestió síncrona de peticions
- Gestió asíncrona de peticions
- Exemples
- Programació i sistemes reactius
El pas de missatges pot implementar-se:
- Dins d'un procés, mitjançant fils. Utilitzant buffers o cues, per exemple.
- Entre processos. Habitualment, es fa utilitzant el paradigma client/servidor i mitjançant xarxes. Un possible mecanisme és l'ús de sòcols, com es podrà veure a la UF Sòcols i serveis. En aquesta comunicació no hi ha compartició de dades mutables, però pot passar que múltiples clients accedeixin simultàniament a un mateix servidor.
En el diagrama pot veure's una implementació entre processos.
Model de programació síncron i asíncron
La comunicació entre les dues parts es pot realitzar de forma síncrona o de forma asíncrona, segons hi hagi un bloqueig E/S (entrada/sortida).
Comes pot veure al diagrama, en la forma síncrona el client espera la resposta del servidor (bloqueig E/S), i mentrestant no fa res. A la forma asíncrona envia la petició, continua treballant i en un moment donat rep la resposta (sense bloqueig E/S).
Quina forma és més convenient? Depèn de les circumstàncies. La forma síncrona és més fàcil d'implementar, però l'asíncrona permet millorar el rendiment del sistema introduint la concurrència.
Comunicació asíncrona
Les peticions asíncrones han de permetre al client conèixer el resultat a posteriori. Alguns solucions possibles:
- Cap: el client només pot saber com va anar fent una o diverses consultes posteriors (polling).
- Una crida de codi: quan acaba la petició, el servidor fa una crida al codi. Podria implementar-se mitjançant callbacks.
- Un missatge: quan acaba la petició, el servidor envia un missatge que pot rebre el client. Aquest missatge pot viatjar en diferents protocols, i se sol implementar mitjançant algun tipus de middleware. Habitualment, els missatges van a parar a cues, que després gestionen els servidors.
Gestió síncrona de peticions
Quan utilitzem el model síncron (amb bloqueig), un sol fil no pot gestionar diverses peticions simultànies. Això vol dir que necessitem crear un fil per gestionar cada petició i retornar la resposta. En diem arquitectura basada en fils.
Habitualment, es limita el nombre de fils que es permeten gestionar simultàniament per evitar el consum excessiu de recursos.
Gestió asíncrona de peticions
Es reprodueix el patró productor-consumidor: els productors són l'origen dels esdeveniments, i només saben que un ha ocorregut; mentre els consumidors necessiten saber que hi ha un nou esdeveniment, i l'han d'atendre (handle). En diem arquitectura basada en esdeveniments.
Algunes tècniques per implementar el servei:
- El patró reactor: les peticions es reben i es processen de forma síncrona, en un mateix fil. Funciona si les peticions es processen ràpidament.
- El patró proactor: les peticions es reben i es divideix el processament asíncronament, introduint concurrència.
A Java tenim Vert.x, una implementació multireactor (amb N bucles d'esdeveniments).
Una altra tècnica per a gestionar peticions asíncrones és el model d'actors. Aquest model permet crear programes concurrents utilitzant actors no concurrents.
- Un actor és una unitat de computació lleugera i desacoblada.
- Els actors tenen estat, però no poden accedir a l'estat d'altres actors.
- Es pot comunicar amb altres actors mitjançant missatges asíncrons immutables.
- L'actor processa els missatges seqüencialment, evitant contenció sobre l'estat.
- Els missatges poden estar distribuïts per la xarxa.
- No es pressuposa cap ordre concret en els missatges.
A Java, tenim un exemple de llibreria: Akka.
Exemples
Una forma d'implementar-lo és passar missatges entre fils mitjançant l'ús d'una cua sincronitzada. Pot haver-hi un o més productors i un o més consumidors. La cua ha de ser thread-safe. A Java, les implementacions de BlockingQueue, ArrayBlockingQueue i LinkedBlockingQueue, en són exemples. Els objectes a aquestes cues han de ser d'un tipus immutable.
Buffer asíncron (cua)
En aquest exemple, un fil productor envia treballs (1, 2, 3, 4) a un fil consumidor mitjançant una cua thread-safe. La mida màxima de la cua es 2.
Les accions són:
- put (prod): afegir un treball, esperant si no hi ha prou espai.
- take (cons): llegir un treball per processar-lo, i esperar si no hi ha cap.
Flux de crides de la impressora asíncrona
De vegades, les peticions fan referència a un recurs compartit que no permet el seu ús per més d'un client alhora. En aquests casos, es pot implementar una cua que gestioni les peticions de forma asíncrona:
- El client realitza la petició asíncrona, i més endavant pot rebre la resposta o confirmació de la petició.
- El servidor registra la petició en una cua, que va atenent per ordre a un fil independent.
La impressora és un únic fil (servidor) que va llegint els treballs afegits a la cua per diferents usuaris (fils), i atenent-los.
També podríem tenir més d'una cua, si hi ha la possibilitat de tenir més d'un punt per atendre les peticions (diverses impressores).
Programació i sistemes reactius
La programació passiva és la tradicional als dissenys OO: un mòdul delega en un altre per a produir un canvi al model.
L'alternativa plantejada es diu programació reactiva, on utilitzem callbacks per a invertir la responsabilitat.
El terme "reactiu" s'utilitza en dos contextos:
- La programació reactiva està basada en esdeveniments (event-driven). Un esdeveniment permet el registre de diversos observadors. Habitualment funciona de forma local.
- Els sistemes reactius generalment es basen en missatges (message-driven) amb un únic destí. Es corresponen més sovint a processos distribuïts que es comuniquen a través d'una xarxa, potser com a microserveis que cooperen.
En l'exemple de la cistella de la compra, podem veure com implementar-ho amb programació passiva i reactiva:
- Amb passiva, la cistella actualitza la factura. Per tant, la cistella és la responsable del canvi i depèn de la factura.
- Amb reactiva, la factura rep un esdeveniment de producte afegit i s'actualiza a si mateixa. La factura depèn de la cistella, ja que li ha de dir que vol sentir els seus esdeveniments.
Pros i contres:
- La programació reactiva permet entendre millor com funciona un mòdul: només cal mirar al seu codi, ja que és responsable d'ell mateix. Amb la passiva és més difícil, ja que cal mirar-se els altres mòduls que el modifiquen.
- Per altra banda, amb programació passiva és més fàcil entendre a quins mòduls afecta un: mirant quines referències es fan. Amb programació reactiva cal mirar-se quins mòduls generen un cert esdeveniment.
La programació reactiva és asíncrona i sense bloqueig. Els fils que busquen recursos compartits no bloquegen l’espera que el recurs estigui disponible. En el seu lloc, continuen la seva execució i són notificats després quan el servei s'ha completat.
Les extensions reactives permeten que llenguatges imperatius, com Java, puguin implementar programació reactiva. Ho fan utilitzant programació asíncrona i streams observables, que emeten tres tipus d'esdeveniments als seus subscriptors: següent, error i completat.
Des de Java 9 s'han definit els streams reactius utilitzant el patró Publish-Subscribe (molt semblant al patró observador) mitjançant les interfícies Flow. Les implementacions més utilitzades són Project Reactor (p. ex. Spring WebFlux) i RxJava (p. ex. Android).
Per altra banda, un sistema reactiu és un estil d'arquitectura que permet que diverses aplicacions puguin comportar-se com una sola, reaccionant al seu entorn, mantenint-se al corrent els uns dels altres, i permetent la seva elasticitat, resiliència i responsivitat basats (habitualment) en cues de missatges dirigits a receptors concrets (vegeu el Reactive Manifesto). Una aplicació dels sistemes reactius són els microserveis.
Tant els patrons reactor/proactor com el model d'actors permeten implementar sistemes reactius.
Sòcols i serveis
Resultats d'aprenentatge:
- Programa mecanismes de comunicació en xarxa emprant sòcols i analitzant l’escenari d’execució.
- Desenvolupa aplicacions que ofereixen serveis en xarxa, utilitzant llibreries de classes i aplicant criteris d’eficiència i disponibilitat.
Referències
- OSI Model
- HTTP (Mozilla)
- HttpURLConnection
- reqbin.com
- A guide to Java sockets
- Do a Simple HTTP Request in Java
- Core Java Networking (eugenp)
- Read an InputStream using the Java Server Socket
- REST vs Websockets (baeldung)
- A Guide to the Java API for WebSocket (baeldung)
- All About Sockets (The Java Tutorials)
- SSL Handshake Failures (baeldung)
- HTTP: The Protocol Every Web Developer Must Know (part 1)
- HTTP: The Protocol Every Web Developer Must Know (part 2)
- REST API Tutorial
- Blocking I/O and non-blocking I/O
- A Guide to NIO2 Asynchronous Socket Channel
- Java NIO Tutorial (Jenkov)
- How Single-Page Applications Work
- How Single-Page Web Applications actually work?
- What Is a Single Page Application and Why Do People Like Them so Much?
- Guía práctica para la publicación de Datos Abiertos usando APIs
- Local-first software
Protocols
Els protocols d'Internet tenen més d'un model. El model OSI ens dona una organització per capes:
- Física: transmissió i recepció de bits sobre el mitjà físic.
- Enllaç: transmissió fiable de trames entre dos nodes. PPP.
- Xarxa: transmissió de paquets sobre una xarxa multi-node, amb adreçament, encaminament i control de tràfic. IP.
- Transport: transmissió de segments de dades entre punts d'una xarxa. TCP, UDP.
- Sessió: gestió de sessions.
- Presentació: traducció dels protocols cap a una aplicació. MIME, SSL.
- Aplicació: APIs d'alt nivell. HTTP, Websockets.
Com a programadors, podem involucrar-nos a diferents nivells del model:
- Construint protocols basats en TCP, UDP mitjançant Java Sockets (nivell 4 i mig).
- Accedint a aplicacions web HTTP (nivell 7), implementades als ports 80 o 443 (segur).
Els protocols de nivell 4 i mig estàndards es poden veure a aquesta llista.
IP versió 4
Les adreces IPv4 utilitzen 32 bits, i tenen rangs assignats a diferents propòsits:
- Classe A: de 1.x.x.x a 127.x.x.x (subnet mask 255.0.0.0). El rang 127.x.x.x està reservat per al loopback.
- Classe B: de 128.0.x.x a 191.255.x.x (subnet mask 255.255.0.0).
- Classe C: de 192.0.0.x a 223.255.255.x (subnet mask 255.255.255.0).
- Classe D: de 224.0.0.0 a 239.255.255.255. Reservades per a multicasting.
- Classe E: de 240.0.0.0 a 255.255.255.254. Reservades per a recerca.
D'aquestes, es consideren IP privades:
- 10.0.0.0 a 10.255.255.255
- 172.16.0.0 a 172.31.255.255
- 192.168.0.0 a 192.168.255.255
Aquests són els tipus d'encaminaments disponibles:
Unicast: adreces de classe A, B i C. Transport TCP i UDP.
Broadcast: Adreça del host (sufix) amb tot 1's. Transport UDP. No travessen els encaminadors, i les reben totes les màquines. No disponible a IPv6.
Multicast: adreces de classe D. Transport UDP. Una màquina ha d'escoltar per rebre la comunicació. Disponible a xarxes locals.
IP versió 6
IPv6 és la versió 6 del Protocol d'Internet (IP), i està dissenyat per substituir l'actual IPv4 a internet, però encara no està suportat completament. Les adreces utilitzen 128 bits, que es mostren en grups de 4 dígits hexa.
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
IPv6 no té classes. Quan es fa subxarxes, la notació /NN indica la longitud del prefix de xarxa. Per exemple:
2001:0db8:1234::/64
La notació ::
indica un o més grups de valor 0. Només hi pot haver un ::
en una adreça.
El loopback es representa com ::1/128
o, simplement, ::1
.
Els tipus d'encaminament d'IPv6 són unicast, multicast i anycast (el tipus broadcast no existeix a IPv6). Anycast permet enviar un missatge a un group on només contesta un, el més proper.
Disseny de protocols
Introducció al disseny
Els protocols regeixen la comunicació entre un client i un servidor. Un protocol ben dissenyat garanteix fiabilitat, claredat i extensibilitat. El disseny del protocol implica definir:
- Format del missatge: Estructura de peticions i respostes.
- Flux de treball: seqüència d'interaccions entre client i servidor.
- Conversió de flux de bytes: codificació i descodificació de missatges per a la transmissió.
En la comunicació de xarxa, les dades es transmeten com un flux de bytes. Sense una estructura definida, és impossible determinar on acaba un missatge i on comença el següent, o com interpretar el contingut d'un missatge. Un protocol preveu:
- Límits: marcadors per identificar l'inici i el final dels missatges.
- Regles de descodificació: instruccions per interpretar el flux de bytes en brut en dades significatives.
- Coherència: un format estandarditzat per a la comunicació.
Per exemple, un client que envia una sol·licitud d'inici de sessió i un servidor que respon ha d'acordar com codificar el nom d'usuari, la contrasenya i l'estat de resposta en un flux de bytes, així com com tornar-lo a descodificar.
Objectius:
- Estandarditzar la comunicació entre client i servidor.
- Minimitzar errors i ambigüitats.
- Admet camps de dades flexibles i de longitud variable.
Disseny d'alt nivell
Abans de dissenyar el protocol, identificar:
- El servei que s'ha d'implementar (p. ex., transferència de fitxers, autenticació o xat).
- Tipus de peticions i respostes necessàries.
A continuació, descriure la interacció típica:
- Configuració de la connexió: com el client i el servidor estableixen una connexió.
- Intercanvi petició-resposta: defineix la seqüència de missatges.
- Desactivació de la connexió: defineix com acaba la connexió.
Un missatge del protocol pot tenir:
- Capçalera, metadades que defineixen el tipus de missatge i què esperem.
- Càrrega útil o payload, amb el contingut real del missatge. Poden contenir camps de mida fixa o mida variable (amb prefixos de longitud).
Exemple de missatge:
Camp | Descripció |
---|---|
Longitud de la capçalera | Mida fixa, indica la mida de la capçalera (en bytes). |
Tipus de missatge | Especifica el tipus de missatge (p. ex., sol·licitud, resposta). |
Longitud de càrrega útil | Mida de la càrrega útil en bytes. |
Càrrega útil | Camp de mida variable que conté les dades reals. |
Exemple de capçalera:
- Longitud de la capçalera: 2 bytes
- Tipus de missatge: 1 byte (p. ex., 0x01 per a la sol·licitud, 0x02 per a la resposta)
- Longitud de càrrega útil: 4 bytes (sencer big-endian)
Exemple de payload per a inici de sessió:
Camp | Tipus | Llargada |
---|---|---|
Longitud del nom d'usuari | Sencer | 2 bytes |
Nom d'usuari | Cadena | Variable |
Longitud de la contrasenya | Sencer | 2 bytes |
Contrasenya | Cadena | Variable |
El procés de codificació i decodificació transforma dades de l'àmbit de la programació en fluxos de dades en fluxos de bytes, i de nou a dades. Aquest procés s'anomena també serialització i deserialització, i hauria de ser independent del llenguatge de programació, el sistema operatiu o altres condicionants.
Disseny de protocols per a TCP i UDP
En implementar un protocol, és essencial entendre les característiques específiques del protocol de transport subjacent.
TCP (Protocol de control de transmissió)
TCP proporciona un lliurament fiable, ordenat i verificat d'errors de fluxos de dades.
Característiques clau
- Fiabilitat: TCP assegura que tots els missatges s'entreguen sense pèrdua.
- Preservació de l'ordre: les dades arriben al receptor en el mateix ordre en què es van enviar.
- Orientat al flux: els missatges es transmeten com un flux continu de bytes, que requereixen mecanismes addicionals per definir els límits del missatge.
Consideracions sobre l'estructura del missatge
- Límits explícits: utilitzeu prefixos de longitud o delimitadors per separar els missatges dins del flux.
- Reassemblatge de fragments: cal entendre que els missatges han d'enviar-se en unitats de transmissió, i que internament, l'API de Java s'encarrega de l'assemblatge i reassemblatge quan cal dividir-los.
- Amb estat: feu un seguiment de l'estat de la connexió per gestionar els reintents o els temps d'espera de les dades no reconegudes.
UDP (Protocol de datagrama d'usuari)
UDP és un protocol sense connexió que ofereix una comunicació més ràpida però menys fiable.
Característiques clau
- Sense fiabilitat: es poden perdre missatges o lliurar-se fora de servei sense cap retransmissió automàtica.
- Sense connexió: no cal establir una connexió abans d'enviar dades, per la qual cosa és més ràpid que TCP.
- Sense preservació de l'ordre: UDP no garanteix que els missatges arribin en el mateix ordre en què es van enviar.
- Baixa sobrecàrrega: les capçaleres UDP són més petites en comparació amb les capçaleres TCP, la qual cosa la fa més eficient pel que fa a l'ús de l'ample de banda.
Consideracions sobre l'estructura del missatge:
- Sense límits incorporats: com que UDP està orientat a missatges, cada paquet UDP és un sol missatge. Si necessiteu enviar diversos missatges en un paquet, heu de definir límits a nivell d'aplicació (p. ex., utilitzant delimitadors o prefixos de longitud).
- Límits de mida dels missatges: els paquets UDP solen tenir una mida limitada (normalment uns 65.535 bytes), però pot ser que es necessiti fragmentació a nivell d'aplicació per a missatges més grans.
- Sense estat: cada paquet UDP és independent, de manera que no cal mantenir els estats de connexió entre l'emissor i el receptor.
Escollir entre TCP i UDP
Quan es dissenya un protocol, l'elecció entre TCP i UDP depèn de diversos factors:
- Requisits de fiabilitat: si s'ha de garantir l'entrega de missatges (per exemple, transaccions financeres, transferències de fitxers), TCP és la millor opció a causa de les seves capacitats de verificació d'errors i retransmissió.
- Necessitats de rendiment: si una latència baixa és crítica i es pot tolerar la pèrdua ocasional de missatges (per exemple, la transmissió de veu o de vídeo en temps real), UDP pot ser una millor opció a causa de la seva menor sobrecàrrega i de la seva entrega més ràpida.
- Mida i freqüència del missatge: per a protocols amb missatges grans i poc freqüents (p. ex., transferència de fitxers), el disseny orientat al flux de TCP funciona bé. Per a missatges més petits i freqüents (per exemple, consultes DNS), la senzillesa i l'eficiència d'UDP són avantatjoses.
Protocol HTTP
HTTP és un protocol de nivell aplicació per a sistemes col·laboratius i distribuïts. És el component principal de la web, gràcies a l'ús de documents d'hipertext. HTTP/1.1, la versió actual, està implementat mitjançant TCP al transport. La versió 2 ja està estandaritzada, i la 3 funcionarà sobre UDP.
La versió segura d'HTTP es diu HTTPS, o també HTTP sobre TLS, el protocol criptogràfic per a la transmissió segura.
Sessió
Una sessió és una seqüència de peticions/respostes. Comença mitjançant l'establiment d'una connexió TCP a un port d'un servidor (habitualment 80). El servidor contesta habitualment amb un codi, del tipus "HTTP/1.1 200 OK", i amb un cos, que normalment conté el recurs demanat.
HTTP es un protocol sense estat, tot i que algunes aplicacions utilitzen mecanismes per emmagatzemar informació. Per exemple, les cookies.
Missatges
Una petició conté, habitualment:
- Una línia de petició, amb un mètode. Exemple: GET /images/logo.png HTTP/1.1
- Camps de la capçalera de la petició. Exemple: Accept-Language: ca
- Una línia buida.
- Un cos opcional. Exemple: per fer un POST.
Mètodes de petició:
- GET: el mètode habitual per obtenir un recurs. No té cos.
- POST: el mètode utilitzat per enviar un cos al servidor. S'utilitza als formularis.
- PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH son altres mètodes utilitzats.
Una resposta conté:
- Una línia d'estat. Exemple: HTTP/1.1 200 OK.
- Camps de la capçalera de la resposta. Exemple: Content-Type: text/html
- Una línia buida.
- Un cos opcional. Exemple: per a un GET, el contingut requerit.
Els codis d'estat poden ser del tipus:
- Informació (1XX).
- Èxit (2XX). Exemple: 200 OK.
- Redirecció (3XX). Exemple: 301 Moved Permanently.
- Error de client (4XX). Exemple: 404 Not Found.
- Error de servidor (5XX). Exemple: 500 Internal Server Error.
Podem utilitzar el programa telnet per conectar-nos a un servidor web HTTP i enviar una comanda GET.
$ telnet maripili.es 80
Trying 217.160.0.165...
Connected to maripili.es.
Escape character is '^]'.
GET / HTTP/1.0
Host: maripili.es
Això provoca la resposta del servidor:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Connection: close
Date: Wed, 16 Feb 2022 08:12:31 GMT
Server: Apache
<!DOCTYPE html>
<html lang="es">
...
</html>
Connection closed by foreign host.
Eines
Tenim tres eines per depurar protocols HTTP: netcat, curl i l'inspector dels navegadors.
Netcat permet connectar-se a un port i fer una conversa, utilitzant les canonades. Si s'indica -u utilitza UDP, si no, TCP. Per exemple, per a accedir al servei echo de la nostra màquina:
$ nc localhost 7
CURL permet obtenir la resposta d'una URL a la xarxa.
$ curl -I http://maripili.es
(GET, veure headers)
HTTP/1.1 200 OK
Date: Fri, 05 Apr 2019 05:03:23 GMT
Server: Apache
X-Logged-In: False
P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: 4af180c8954a0d5a1965b5b1b23ccbc5=pc1jg8f5kqigd71iec5a5lm7k5; path=/
X-Powered-By: PleskLin
Content-Type: text/html; charset=utf-8
$ curl http://maripili.es
(GET, contingut de la pàgina web)
$ curl -v http://maripili.es
(GET, headers i contingut)
$ curl -d "key1=val1&key2=val2" http://maripili.es/contacto/
(POST)
Implementació a Java
URL i HttpURLConnection
La classe URL fa referència a un recurs a la WWW. Un recurs genèric pot tenir la següent forma.
Veiem un exemple per al protocol HTTP:
https://www.example.com/test?key1=value1&key2=value2
En aquest cas, tenim que:
- l'esquema és https
- el host és www.example.com
- el port és 80, però no s'indica, ja que és el valor per defecte al protocol HTTP
- el camí (path) és test
- la query és key1=value1&key2=value2
A Java es pot construir una URL amb:
URL url = new URL(String spec)
Un cop fet això, podem accedir a cada part de l'URL amb els mètodes getHost(), getPath(), getPort(), getProtocol(), getQuery(), etc.
Els dos mètodes més importants per interactuar amb l'URL són:
URLConnection openConnection()
: retorna una connexió al recurs remot.InputStream openStream()
: retorna un InputStream per a llegir el recurs remot.
La classe URLConnection
és abstracta, i si hem accedir a un recurs HTTP llavors l'objecte serà una instància de HttpURLConnection
.
openStream
Per llegir una pàgina web que es trobi a l'URL d'una cadena anomenada urlText, podem fer:
URL url = new URL(urlText);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
Per llegir un arxiu:
BufferedInputStream in = new BufferedInputStream(new URL(urlText).openStream());
openConnection
Amb openConnection podem accedir als mètodes del protocol HTTP i els codis d'estat que es retornen o el tipus de contingut.
Aquest és un mètode GET:
URL url = new URL(urlText);
HttpURLConnection httpConn = ((HttpURLConnection) url.openConnection());
httpConn.setRequestMethod("GET"); // opcional: GET és el mètode per defecte
int responseCode = httpConn.getResponseCode();
String contentType = httpConn.getContentType();
BufferedReader in = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));
// falta llegir in: resposta del servidor
Aquest és un mètode POST:
URL url = new URL(urlText);
HttpURLConnection httpConn = ((HttpURLConnection) url.openConnection());
httpConn.setRequestMethod("POST");
httpConn.setDoOutput(true);
OutputStreamWriter out = new OutputStreamWriter(httpConn.getOutputStream());
out.write("propietat1=valor1&propietat2=valor2"); // valors dels paràmetres del POST
out.close();
int responseCode = httpConn.getResponseCode();
String contentType = httpConn.getContentType();
BufferedReader in = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));
// falta llegir in: resposta del servidor
Sòcols
- TCP i UDP
- Protocol exemple: ECHO
- Protocol exemple: SMTP
- Comunicació TCP
- Comunicació UDP
- Timeouts
- Tancament
- Comunicació asíncrona
Un sòcol és un enllaç de doble sentit que permet comunicar dos programaris que són a la xarxa.
Aquests dos programaris fan dues funcions: la del client i la del servidor. El servidor proveeix algun servei des d'un lloc conegut (adreça IP + port), i el client accedeix a aquest servei. Aquest servei ha d'implementar un protocol ben definit, sigui un estàndard o un de dissenyat a mida.
Els números de ports són:
- El rang 0 a 1023 són els ports coneguts (well-known) o de sistema. En Linux, cal ser administrador per tenir un servei en aquests ports.
- El rang 1024-49151 són els ports registrats, assignats per IANA.
- El rang 49152–65535 són ports dinàmics o privats, o de vida curta.
Com que els dos programaris treballen en l'àmbit del protocol, al codi dels dos programaris no hi ha dependències mútues. Però és habitual que qui implementa el protocol proveeixi d'una llibreria de client per poder accedir al servei. Això permet reduir el codi que un client ha d'escriure, i assegura que utilitzarà correctament el protocol. A Java, la llibreria de client es materialitza mitjançant un arxiu jar i una documentació d'ús.
TCP i UDP
Es poden utilitzar els protocols TCP o UDP. TCP està orientat a connexió, i UDP no. Això vol dir que TCP requereix un pas previ de connexió entre el client i el servidor per tal de comunicar-se. Un cop establerta la connexió, TCP garanteix que les dades arribin a l’altre extrem o indicarà que s’ha produït un error.
En general, els paquets que han de passar en l'ordre correcte, sense pèrdues, utilitzen TCP, mentre que els serveis en temps real on els paquets posteriors són més importants que els paquets més antics utilitzen UDP. Per exemple, la transferència d’arxius requereix una precisió màxima, de manera que normalment es fa mitjançant TCP, i la conferència d’àudio es fa freqüentment a través d’UDP, en què pot ser que no es notin les interrupcions momentànies.
TCP necessita uns paquets de control per a establir la connexió en tres fases: SYN, SYN + ACK i ACK. Cada paquet enviat es contesta amb un ACK. I finalment, es produeix una desconnexió des de les dues bandes amb FIN + ACK i ACK.
UDP, en canvi, només transmet els paquets de petició / resposta, sense cap control sobre la transmissió.
Protocol exemple: ECHO
A continuació es pot veure la visualització del protocol ECHO amb Wireshark, tant per a la implementació TCP com la UDP.
Captura TCP (petició i resposta)
Captura UDP (petició i resposta)
Protocol exemple: SMTP
SMTP és un protocol que funciona al port 25, sobre TCP. El client envia comandes, i el servidor respon amb un codi d'estat.
A continuació, veiem una conversa (C: client / S: servidor). Tota aquesta conversa es manté sobre una connexió oberta.
C: <client connects to service port 25>
C: HELO snark.thyrsus.com la máquina que envia s'identifica
S: 250 OK Hello snark, glad to meet you el receptor accepta
C: MAIL FROM: <esr@thyrsus.com> identificació de l'usuari que envia
S: 250 <esr@thyrsus.com>... Sender ok el receptor accepta
C: RCPT TO: cor@cpmy.com identificació del destí
S: 250 root... Recipient ok el receptor accepta
C: DATA
S: 354 Enter mail, end with "." on a line by itself
C: Scratch called. He wants to share
C: a room with us at Balticon.
C: . final de l'enviament multi-línia
S: 250 WAA01865 Message accepted for delivery
C: QUIT l'emissor s'acomiada
S: 221 cpmy.com closing connection el receptor es desconnecta
C: <client hangs up>
Comunicació TCP
Java té dues classes del paquet java.net que ho permeten:
Socket
: implementació del sòcol client, que permeten comunicar dos programaris a la xarxa.ServerSocket
: implementació del sòcol servidor, que permet escoltar peticions rebudes des de la xarxa.
El funcionament es reflecteix en les següents dues imatges: primer el client demana una connexió, i després el servidor l'accepta, i s'estableix.
Servidor a majúscules
Aquest servidor escolta línies de text i retorna la versió en majúscules.
La comunicació comença amb la petició de connexió del client, i l'acceptació del servidor. Aquestes dues accions creen un sòcol compartit del tipus Socket, sobre el qual, tan el client com el servidor, poden utilitzar els mètodes:
getInputStream()
getOutputStream()
El client pot enviar cadenes de text, que el servidor convertirà a majúscules.
El protocol que ens hem inventat estableix que la comunicació s'acaba quan el client envia una línia buida. a la qual el servidor contesta amb un comiat.
Aquest podria ser un codi del servidor que implementa el protocol descrit utilitzant TCP.
ServerSocket serverSocket = new ServerSocket(PORT);
Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out.println("hola!");
String text;
while ((text = in.readLine()).length() > 0)
out.println(text.toUpperCase());
out.println("adeu!");
clientSocket.close();
serverSocket.close();
Pots provar-ho mitjançant la comanda netcat (nc).
Com seria el protocol d'aquest servei? En pseudocodi:
- Quan et connectes al servidor, envia una línia amb una salutació.
- Per cada línia que envies, et retorna la mateixa línia en majúscules.
- Quan envies una línia en blanc, et contesta amb el comiat, i es desconnecta.
A continuació, es pot veure un client que accedeix a aquest servei, implementant aquest protocol.
Socket clientSocket = new Socket(HOST, PORT);
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String salutacio = in.readLine();
System.out.println("salutacio: " + salutacio);
for (String text: new String[]{"u", "dos", "tres"}) {
out.println(text);
String resposta = in.readLine();
System.out.println(text + " => " + resposta);
}
out.println();
String comiat = in.readLine();
System.out.println("comiat: " + comiat);
in.close();
out.close();
clientSocket.close();
Comunicació basada en text
Als exemples que hem vist, PrintWriter
i BufferedReader
són els objectes que permeten utilitzar cadenes de text sobre l'OutputStream
i l'InputStream
respectivament, que són mitjans de comunicació binària.
El més correcte en aquests casos seria indicar quin és el Charset amb que codifiquem els Strings. Si volguèssim utilitzar UTF-8, seria així:
Charset UTF8 = StandardCharsets.UTF_8;
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF8), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), UTF8));
Relacionat amb el charset, tenim la conversió des de i cap a String a partir de dades binàries, que podem realitzar a UDP. Aquestes són les dues operacions:
byte[] strBytes = "Hola, món!".getBytes(UTF8);
String str = new String(strBytes, UTF8);
Comunicació UDP
Amb UDP no hi ha connexió: simplement s'envien paquets (Datagrames) amb destinació un servidor UDP. Si volem respondre, cal conèixer l'adreça i port destí, que pot obtenir-se del paquet rebut.
Un servidor es pot crear amb:
DatagramSocket socket = new DatagramSocket(PORT)
El client funciona exactament igual, però el socol es crea amb:
DatagramSocket socket = new DatagramSocket();
Per rebre un paquet de mida màxima MIDA:
byte[] buf = new byte[MIDA];
DatagramPacket paquet = new DatagramPacket(buf, MIDA);
socket.receive(paquet);
// dades a paquet.getData() i origen a paquet.getAddress() i paquet.getPort()
Per enviar un paquet al servidor:
InetAddress address = InetAddress.getByName(HOST);
paquet = new DatagramPacket(buf, MIDA, address, PORT);
socket.send(paquet);
Per enviar un paquet de resposta a un client, hem d'utilitzar el port que hi ha al paquet que ens ha enviat prèviament:
InetAddress address = paquetRebut.getAddress();
int port = paquetRebut.getPort();
DatagramPacket paquetResposta = new DatagramPacket(buf, buf.length, address, port);
socket.send(paquet);
Concurrència
Com podem fer que els servidors acceptin peticions concurrents de diversos clients?
Ho vam veure a la UF "Processos i fils". Caldria atendre cada petició a un fil diferent. Per exemple, per al cas de TCP:
Executor executor = Executors.newFixedThreadPool(NFILS);
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) {
final Socket clientSocket = serverSocket.accept();
Runnable tasca = new Runnable() {
public void run() {
atendrePeticio(clientSocket);
}
};
executor.execute(tasca);
}
D'aquesta manera, no fem esperar nous clients quan atenem un.
Timeouts
Quan utilitzem sòcols TCP, les operacions de connexió i enviament de dades requereixen la confirmació de l'altra part. Per defecte, l'API que hem vist no té temporitzadors (timeouts), i es bloqueja indefinidament.
També hi ha l'operació UDP de llegir un datagrama, que per defecte es bloqueja esperant la resposta.
Aquestes són les operacions de connexió TCP:
new Socket(host, port...)
: creació d'un sòcol i connexió sense timeout.new Socket()
: creació d'un sòcol no connectat (no hi ha bloqueig).Socket.connect(SocketAddress, timeout)
: connexió d'un sòcol no connectat amb un timeout.
Un cop creat un sòcol TCP, sigui de client (Socket
) o de servidor (ServerSocket
), podem canviar la temporització utilitzant Socket.setSoTimeout(int timeout)
. Quan es cumpleix el temporitzador, es llença una excepció de tipus SocketTimeoutException
. Aquest timeout (en mil·lisegons) afecta les següents operacions TCP/UDP:
ServerSocket.accept()
: acceptació d'una connexió d'un client al servidor TCP.SocketInputStream.read()
: lectura de dades a un sòcol TCP.DatagramSocket.receive()
: lectura d'un datagrama UDP.
Tancament
Aquests són alguns aspectes associats al tancament d'un sòcol TCP:
- Podem comprovar si una connexió TCP està tancada amb
Socket.isClosed()
iServerSocket.isClosed()
. - Si tanquem la connexió d'un sòcol amb
Socket.close()
, es tanquen automàticament els seus streamsInputStream
iOutputStream
. - Si tanquem la connexió de qualsevol dels seus streams, es tanca automàticament la del sòcol associat.
- Si fem un
ServerSocket.close()
i s'està esperant amb unServerSocket.accept()
, s'interromprà i hi haurà una SocketException. - Si un sòcol està esperant amb un
SocketInputStream.read()
i el sòcol de l'altra banda es tanca, elread()
s'interromprà i hi haurà una SocketException. - Si un sòcol està esperant en un canal de text amb un
BufferedReader.read()
oBufferedReader.readLine()
i el sòcol de l'altra banda es tanca, es retornarà -1 i null, respectivament.
Comunicació asíncrona
Basada en les llibreries NIO. Veure aquest post.
Serveis
Arquitectures
Una aplicació pot veure's com quatre components: les dades, la lògica d'accés a dades, la lògica de l'aplicació i la presentació. Aquests components es poden distribuir de moltes formes:
- basades en servidor: el servidor fa pràcticament tota la feina. Els clients són molt lleugers.
- basades en client: el client fa pràcticament tota la feina. El servidor només guarda les dades.
- peer-to-peer: les màquines fan de client i servidor i comparteixen la feina, que fan integralment.
- client/servidor: l'arquitectura dominant. La lògica de l'aplicació i d'accés a dades pot estar distribuïda entre client i servidor. Poden tenir múltiples capes: 2, 3, N. Permet integrar aplicacions de diferents proveïdors utilitzant protocols estàndard. Aquesta és l'arquitectura dominant.
L'arquitectura client/servidor està centrada habitualment en les dades: la lògica de negoci s'interposa entre aquestes dades i la interfície d'usuari, habitualment web. Un exemple de patró és el MVC (model/vista/controlador).
Si atenem al criteri d'on es genera l'HTML d'una aplicació web podem tenir:
- El tradicional: l'HTML es genera al servidor.
- El SPA (Single-Page Application): l'HTML es genera al client, i amb el servidor s'intercanvien dades (JSON, habitualment). Al servidor implementem APIs basades en HTTP, que poden compartir-se amb diferents tipus de clients com navegadors o aplicacions per mòbil.
Quan les funcionalitats creixen i s'afegeixen a una solució, tenim el risc de convertir la nostra aplicació en el que s'anomena aplicació monolítica. Algunes solucions arquitecturals proposen solucions:
- Arquitectura de microserveis: proposa serveis completament independents que proporcionen funcionalitats autocontingudes. Tots ells es comuniquen amb protocols lleugers (poden ser heterogenis) basats en REST/HTTP gràcies a un contracte ben establert (API). Basats en l'idea del bucle d'esdeveniments.
- Arquitectura orientada a serveis (SOA): una solució similar a l'anterior, però els serveis es comuniquen utilitzant un middleware més complex anomenat bus de serveis d'empresa (ESB). Basats en coordinació de múltiples serveis al bus.
APIs
El món està cada cop més interconnectat mitjançant APIs que proporcionen serveis. Aquests poden ser públics, per tal d'afegir valor al negoci d'una empresa.
Les APIs es diuen que són gestionades quan tenen un cicle de vida ben definit:
CREADA ➡ PUBLICADA ➡ OBSOLETA ➡ RETIRADA
Només es publiquen un cop estan ben documentades, amb les seves regles de qualitat d'ús, com la limitació d'ús. La forma estàndard i oberta de descriure APIs és mitjançant OpenAPI.
Exemple: API Twitter
HTTP
El protocol HTTP s'implementa a sobre de TCP, habitualment al port 80. Això ens permet implementar un servidor HTTP utilitzant sòcols.
Per escriure el servidor, hem de ser capaços de llegir una petició HTTP i de respondre.
Petició
request = Request-Line
*(<message-header>)
CRLF
[<message-body>]
La Request-Line té el format:
Request-Line = Method URI HTTP-Version
Els mètodes més habituals són GET/POST. La versió, HTTP/1.1. La URI és només la part del camí (path) absolut.
Els headers més habituals són:
- Host (obligatori): especifica el nom de domini del servidor.
- Accept: informa al servidor sobre els tipus de dades que es poden rebre.
El message-body està buit per al mètode GET, i conté els camps d'un formulari per al mètode POST.
Quan és POST, s'envia el header Content-Type amb els valors:
- application/x-www-form-urlencoded: valors codificats en tuples clau-valor separades per &, amb un = entre clau i valor. Els valors no alfanumèrics s'han de codificar en codi percent. En Java es pot fer amb
URLEncoder.encode(query, "UTF-8")
. - multipart/form-data: transmisió de dades binàries, per exemple, un arxiu.
- text/plain: format text.
Resposta
response = Status-Line
*(<message-header>)
CRLF
[<message-body>]
El Status-Line té el format:
HTTP-Version Status-Code Reason-Phrase
Els codis d'estat ja els vam veure. La Reason-Phrase és un text llegible que explica el codi.
Els headers més habituals són:
- Content-Type: el tipus MIME (media) retornat. Pot incloure el charset. Exemple:
text/html; charset=UTF-8
. - Content-Length: el nombre de bytes del contingut retornat.
- Date: la data del contingut retornat.
- Server: el nom del servidor.
El message-body té el contingut del recurs que s'obté.
Cookies
Les cookies són un mecanisme que permet emmagatzemar parelles clau/valor al navegador des d'un servidor HTTP. Es pot utilitzar per diferents propòsits, per exemple, per identificar una sessió d'un usuari, o bé per seleccionar una preferència de visualització, com pot ser l'idioma.
Hi ha dues capçaleres associades a aquest mecanisme:
- Set-Cookie: capçalera que s'escriu des de la resposta del servidor per a assignar una cookie.
- Cookie: capçalera que es llegeix des de la petició del navegador amb els valors de les cookies que hi ha emmagatzemades.
Per a esborrar una cookie, només cal enviar la cookie buida amb una data al camp "expires" antiga:
- Set-Cookie: nomgaleta=; expires=Thu, 01-Jan-1970 00:00:00 GMT;
Exemple GET/POST d'un formulari HTML: la URI /form mostra un formulari, que s'omple i processa la URI /submit.
Exemple de cookie: la URI /page1 emmagatzema una cookie, que després està disponible a altres pàgines.
Exemple de redirecció: la URI /page1 es redirecciona a /page2.
APIs sobre HTTP
Veurem dos tipus de protocols sobre HTTP: un stateless i un altre stateful.
RESTful API
REST (Representional State Transfer) és un estil d'arquitectura per a sistemes distribuïts. Permet establir comunicació d'aplicacions amb serveis proporcionats a la web. Per tal que una interfície es pugui anomenar RESTful, ha de cumplir una sèrie de principis:
- Ha d'implementar un esquema client/servidor. Això permet desenvolupar-los de forma independent, i reemplaçar-los.
- Ha de ser stateless (sense estat en el servidor). Per tant, l'estat s'ha de conservar al client. Això millora l'escalabilitat, la disponibilitat i el rendiment de l'aplicació.
- S'ha de donar informació al client (de forma implícita o explícita) de si el contingut és cacheable. Així, es pot millorar l'escalabilitat i rendiment.
- Ha de tenir una interfície uniforme. Bàsicament, un recurs ha de associar-se amb una URI que permeti accedir a les seves dades.
- Ha de dissenyar-se com a un sistema per capes. El client no pot saber específicament l'arquitectura del servei o on es troben les dades, per exemple.
- Opcionalment, el client pot demanar codi al servidor, per simplificar la seva implementació (poc habitual).
Encara que no és obligatori, un servei RESTful sovint utilitza HTTP com a protocol. En aquest cas, els cos de les peticions i les respostes solen tenir el format XML o bé JSON.
Si ens fixem en les operacions CRUD habituals, hi ha una convenció de com utilitzar els mètodes HTTP utilitzant els codis d'estat 200, 201, 204, 400, 404:
- GET: llegir (idempotent).
- POST: crear (no cacheable).
- PUT: actualitzar/reemplaçar.
- DELETE: esborrar.
- PATCH: modificació parcial.
Com que el protocol és sense estat (stateless), la autenticació/autorització ha de produir-se per cada petició. Les pràctiques recomanades inclouen utilitzar canals segurs, i no exposar mai dades a la URL. També es recomana l'ús d'Oauth.
L'ús de tokens, o paraules d'accés, és habitual als sistemes d'autenticació. El funcionament amb token és el següent:
- L'usuari o aplicació client accedeix al servei d'autenticació.
- Si és correcta, el servidor genera un token que envia al client.
- L'usuari accedeix als recursos amb el seu token.
Streaming API
Un protocol de tipus streaming és justament una inversió del RESTful. No es tracta d'una conversació. Es tracta d'obrir una connexió entre un client i l'API, on el client va rebent els nou resultats quan es produeixen, en temps real.
La seva naturalesa és stateful, ja que l'API envia els resultats en funció del perfil del client i/o de les regles de filtratge que hagi establert.
És habitual utilitzar el format JSON. En aquest cas, s'utilitza el format text i es poden delimitar els missatges amb fi de línia.
Un exemple és el de Twitter.
Criptografia
Resultats d'aprenentatge (compartit amb Seguretat):
- Protegeix les aplicacions i les dades definint i aplicant criteris de seguretat en l’accés, emmagatzematge i transmissió de la informació.
Referències
- Java Security Standard Algorithm Names, JDK 11
- Cryptographic Storage Cheat Sheet
- Security Developer’s Guide, JDK 11 Providers Documentation
- Block cipher mode of operation
- Java Criptography (Jenkov)
- Cryptographic hash function (Wikipedia)
- Public-key cryptography (Wikipedia)
- Java Security 2nd Edition (examples)
- Practical Cryptography for Developers
- Establishing a TLS Connection
- Generating and Verifying Signatures (The Java Tutorials)
- A Deep Dive on End-to-End Encryption: How Do Public Key Encryption Systems Work?
- Java Keytool - Commands and Tutorials
- Java KeyStores - the gory details
- Trusted Timestamping (Wikipedia)
- CyberChef
- Capture The Flag 101
- SideKEK
Conceptes
- Autenticació
- Xifrat
- Estats de les dades
- Tipus de xifrat
- Intercanvi de claus
- Resums de missatge
- Signatures digitals
- Certificats
Principi de Kerckhoffs: "L'efectivitat del sistema no ha de dependre que el seu disseny romangui en secret.".
Autenticació
L'autenticació és el procés de confirmar l'autenticitat reclamada per una entitat. Tenim bàsicament dos tipus:
- Autenticació d'autor: l'autor és qui diu ser. Es pot aconseguir amb una signatura digital.
- Autenticació de dades: les dades no han estat modificades. Es pot aconseguir amb un algorisme de resum de missatge (message digest).
L'autenticació no vol dir que tinguem xifratge.
Xifrat
La criptografia (del grec "kryptos" - amagat, secret - i "graphin" - escriptura. Per tant seria "escriptura oculta") és l'estudi de formes de convertir informació des de la seva forma original cap a un codi incomprensible, de forma que sigui incomprensible pels que no coneguin aquesta tècnica.
En la terminologia de criptografia, trobem els següents elements:
- La informació original que ha de protegir-se i que es denomina text en clar o text pla.
- El xifrat és el procés de convertir el text pla en un text il·legible, anomenat text xifrat o criptograma.
- Les claus són la base de la criptografia, i són cadenes de números amb propietats matemàtiques.
Estats de les dades
En un sistema segur, quan una dada s'ha d'utilitzar (en ús) cal que sigui en pla. Però si està emmagatzemada o transmetent-se, cal mantenir-la secreta. Aquests són els estats:
- En repòs: quan estan emmagatzemades digitalment en un dispositiu.
- En trànsit: quan s'estan movent entre dispositius o punts de les xarxa.
- En ús: quan una aplicació l'estan utilitzant, sense protecció.
Quan protegim les dades en repòs, preparem al nostre sistema per a l'eventualitat que es puguin llegir després d'un atac a la màquina on són. Quan protegim les dades en trànsit, ho fem perquè sabem que qualsevol pot escoltar el tràfic d'una xarxa.
Si s'exposen les nostres dades xifrades, i aconsegueixen accedir a les claus secretes associades (ja sigui al moment o més endavant), podran veure-les en pla.
De vegades podem evitar haver-les de guardar. Per exemple, si les podem demanar cada cop (p. ex. clau mestra), o bé comparar-les amb resums (p. ex. contrasenyes).
Tipus de xifrat
Existeixen dos grans grups d'algorismes de xifrat, en funció de si les claus són úniques o van en parella:
- Simètrics: els algorismes que utilitzen una única clau privada per xifrar la informació i la mateixa per desxifrar-la.
- Asimètrics: els que tenen dues claus, una pública i una altra privada, que permet xifrar amb una qualsevol i desxifrar amb l'altra. S'utilitzen principalment per a dos propòsits:
- Xifrat amb clau pública: un missatge xifrat amb clau pública només es pot desxifrar amb la clau privada.
- Signatura digital: un missatge signat amb la clau privada pot ser verificat per qualsevol amb la clau pública.
La clau simètrica té inconvenients. Primer, necessites una clau per cada parella origen/destí. Segon, necessites una forma segura de compartir-les. La clau asimètrica té l'avantatge que pots compartir la part pública de la clau, ja que sense la part privada no pots fer res, però els algorismes són més lents i tenen limitacions en la mida del missatge a xifrar. Per exemple, RSA té un missatge màxim de floor(n/8)-11 bytes
.
El xifrat també pot ser de bloc o de stream:
- Bloc: xifrat de blocs de mida fixa de dades. Poden ser asimètrics o simètrics.
- Stream: xifrat d'un stream (corrent) de bits o bytes. Són simètrics.
Si no es diu el contrari, parlem només de xifrats en bloc.
Xifrat simètric: les dues parts comparteixen una clau privada
Xifrat asimètric: només el propietari de les claus (el receptor) pot desxifrar les dades
Intercanvi de claus
Com ja hem vist, el xifratge simètric té el problema de compartir la clau entre les dues parts. I l'asimètric, no permet xifrar blocs gaire grans.
La solució és utilitzar els dos tipus de xifrat de forma combinada.
- La criptografia asimètrica ens permet compartir una clau privada de forma segura.
- La criptografia simètrica ens permet xifrar missatges més llargs i més ràpidament.
L'intercanvi de clau més senzill es pot fer amb xifrat de clau pública RSA: una part xifra el secret compartit amb la clau pública de l'altra. El problema és que aquesta acció no és "secreta cap endavant": si algú obté la clau privada en el futur, i ha guardat la conversa secreta, podria desxifrar-la.
No obstant, l'algorisme més comú per fer intercanvi de clau és el Diffie-Hellman (DH), present en el preàmbul de la majoria de les comunicacions amb xifrat simètric.
Per a dues parts A i B, el procés és el següent:
- A i B generen dues parelles de claus pública/privada, Apub/Apri i Bpub/Bpri.
- Les dues parts s'intercanvien les claus públiques Apub i Bpub.
- En privat, cada part combina les claus públiques rebudes amb les privades (Apub+Bpri, Bpub+Apri). La característica essencial de DH és que aquesta combinació genera el mateix secret!
Resums de missatge
Un "message digest" o hash és una seqüència de bytes produïda quan un conjunt de dades passen per un motor. No és sempre necessària una clau perquè aquest motor operi. Alguns coneguts: MD5, SHA-256. Les seves propietats són:
- És determinista (el mateix resultat per la mateixa entrada).
- És ràpida.
- La funció inversa no és viable.
- Un petit canvi d'entrada provoca un gran canvi de sortida.
- Dues entrades diferents no poden tenir el mateix hash.
Un resum ens permet protegir la integritat d'un missatge.
Veiem com funciona SHA-256 a Linux sobre un petit text "hello world!". Si ho proveu, veureu que el resultat és instantani (2). Com que té 256 bits, genera un resum de 32 bytes (en hex). Mireu com, canviar un caràcter, canvia totalment el hash (4).
$ echo 'hello world!' | openssl sha256
(stdin)= ecf701f727d9e2d77c4aa49ac6fbbcc997278aca010bddeeb961c10cf54d435a
$ echo 'hello,world!' | openssl sha256
(stdin)= 4c4b3456b6fb52e6422fc2d1b4b35da2afbb4f44d737bb5fc98be6db7962073f
Si busqueu el resum de "hello world!" o el de "123456" el trobareu a la xarxa. Dues conclusions: per a un algorisme i una entrada, tenim la mateixa sortida (1). No tenim la funció inversa (3), però hi ha taules de resums per a texts que fan la correspondència, i que s'utilitzen per esbrinar credencials.
Si volem protegir la integritat i l'autenticitat, podem utilitzar els MAC (message authentication code). Bàsicament, es tracta de resums segurs xifrats amb una clau privada que cal compartir entre les dues parts per tal de verificar la comunicació.
També tenim les Key Derivation Functions (KDF), una hash que permet derivar un o més secrets a partir d'un altre més petit, com un password. Les KDF permeten estendre claus (key stretching) en altres més llargues.
En el següent diagrama es poden veure dos usos de les KDF:
- Per a guardar un hash d'una contrasenya, i poder comprovar si s'introdueix correctament.
- Per a generar claus d'algorismes simètrics a partir d'una contrasenya.
Signatures digitals
La signatura digital és un mecanisme de xifrat per autentificar informació digital. El mecanisme utilitzat és la criptografia de clau pública. Per això aquest tipus de signatura també rep el nom de signatura digital de clau pública. S'utilitzen per a garantir tres aspectes: autenticitat, integritat i no repudi.
Aquest és el procés per a obtenir una signatura digital:
- Es calcula un resum de missatge per a les dades d'entrada.
- El resum es xifra amb la clau privada.
Aquest és el procés per a verificar una signatura digital:
- Es calcula un resum de missatge per a les dades d'entrada.
- El resum de la signatura digital es desxifra amb la clau pública.
- Es comparen els dos resums. Si són iguals, la signatura és correcta.
Signatura digital: el propietari de les claus (l'emissor) envia la prova de les dades originals
Certificats
Si se signa un document digital utilitzant una clau privada, el receptor ha de tenir la clau pública per verificar la signatura. El problema és que una clau no indica a qui pertany. Els certificats resolen aquest problema: una entitat ben coneguda (Certificate Authority: CA) verifica la propietat de la clau pública que se t'ha enviat.
Un certificat conté:
- El nom de l'entitat per qui s'ha emès el certificat.
- La clau pública d'aquesta entitat.
- La signatura digital que verifica la informació en el certificat, feta amb la clau privada de l'emissor.
Un certificat podria contenir aquesta informació:
"L'autoritat de certificació 2276172 certifica que la clau pública de John Doe és 217126371812".
Problema: l'emissor del certificat té una clau pública en què hem de confiar. I poden estar encadenades. La darrera de la cadena està autosignada per ella mateixa. Llavors, hem d'acceptar certes CA com a confiables, i habitualment Java (igual que els navegadors) tenen una llista de CA confiables.
El certificat de servidor TLS/SSL és el més comú. El client utilitza l'algorisme de validació del camí de certificació:
- El subject (CN) del certificat coincideix amb el domini al qual es connecta.
- El certificat l'ha signat un CA confiable.
Com s'estableix la comunicació segura entre el navegador i un servidor HTTPS?
- Quan el navegador es connecta al servidor es descarrega el seu certificat, que conté la seva clau pública.
- El navegador posseeix les claus públiques de les CA confiables, i comprova si la signatura del certificat s'ha fet per una CA confiable.
- El navegador comprova que el domini que apareix al certificat és el mateix amb el qual es comunica amb el servidor.
- El navegador genera una clau simètrica, la xifra amb la clau pública del servidor i li envia.
- S'acaba el xifrat asimètric i comença el simètric amb la clau compartida.
Criptografia a Java (JCA)
Els motors criptogràfics de Java proporcionen mecanismes per signatures digitals, resums de missatges, etc. Aquests motors estan implementats per proveïdors de seguretat (java.security.Provider
), que es poden visualitzar mitjançant java.security.Security.getProviders()
. Cada motor (java.security.Provider.Service
) té un tipus i un algorisme.
Claus
Podem generar claus de tipus simètric (KeyGenerator
) o asimètric (KeyPairGenerator
).
KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
keyGen.init(size);
SecretKey secretKey = keyGen.generateKey();
Algorismes simètrics típics són DES (56 bits) o AES (128, 192, 256 bits).
KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm);
kpg.initialize(size);
KeyPair kp = kpg.generateKeyPair();
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
Cal indicar l'algorisme. El més habitual és RSA (1024, 2048 bits).
Tant SecretKey, com PublicKey i PrivateKey, són subclasses de java.security.Key
. Totes elles tenen un mètode getEncoded(): la clau en format binari.
Xifrat
Per a poder xifrar, necessitem un objecte javax.crypto.Cipher
:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
El format del paràmetre (transformation) és algorithm/mode/padding. Mode i padding són opcionals: si no s'indiquen, s'utilitza el mode i padding per defecte. Què són?
- Padding: és una tècnica que consisteix a afegir dades de farciment al començament, mig o fi d'un missatge abans de ser xifrat. Això es fa perquè els algorismes estan dissenyats per tenir dades d'entrada d'una mida concreta.
- Mode: defineix com es fa la correspondència entre els blocs d'entrada (en pla) i els de sortida (xifrats). El més senzill és el mode ECB: un bloc d'entrada va a un de sortida. Altres modes, més segurs, fan aleatòria aquesta correspondència.
Després, hem d'inicialitzar l'objecte utilitzant el mode (Cipher.ENCRYPT_MODE
o Cipher.DECRYPT_MODE
) i la clau de xifrat:
cipher.init(Cipher.ENCRYPT_MODE, key); // xifrat
cipher.init(Cipher.DECRYPT_MODE, key); // desxifrat
Finalment, realitzem el xifrat o desxifrat:
byte[] bytesOriginal = textOriginal.getBytes("UTF-8"); // necessito bytes com a entrada
byte[] bytesXifrat = cipher.doFinal(bytesOriginal);
El desxifrat podria ser:
byte[] bytesDesxifrat = cipher.doFinal(bytesXifrat);
// alternativament, si el contingut a desxifrar és una part de l'array:
byte[] bytesDesxifrat = cipher.doFinal(bytesXifrat, inici, longitud);
Alguns modes de xifrat en bloc utilitzen el que s'anomena vector d'inicialització (IV). Es tracta d'un paràmetre aleatori per a l'algorisme de xifrat que fa més difícil relacionar els blocs en funció de les seves entrades. Normalment no cal que sigui secret, només que no es repeteixi amb la mateixa clau.
Per utilitzar aquests modes (p.ex. CBC), cal afegir un nou paràmetre quan s'inicialitza el Cipher. La mida de l'IV és habitualment la mateixa del bloc. Per AES és de 16 bytes (256 bits).
byte[] iv;
// inicialitzar iv amb un generador aleatori com SecureRandom
IvParameterSpec parameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec); // xifrat
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec); // desxifrat
Xifrat de streams
No té sentit xifrar grans quantitats de dades amb mètodes de bloc. Per a aquestes situacions, podem utilitzar el xifrat de streams. Aquest xifrat és sempre simètric.
A Java, tenim les classes CipherInputStream i CipherOutputStream.
CipherInputStream i CipherOutputStream admeten un Cipher simètric de bloc, com per exemple AES/ECB/PKCS5Padding, o bé els modes de tipus feedback CFB8 or OFB8, (8 = blocs de 8 bits), com per exemple AES/CFB8/NoPadding. Els modes feedback necessiten vectors d'inicialització (IV).
Per exemple, si volem obrir un arxiu i xifrar-lo o desxifrar-lo, podem fer-ho així:
FileInputStream in = new FileInputStream(inputFilename);
FileOutputStream fileOut = new FileOutputStream(outputFilename);
CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
Llavors, caldria copiar el stream in a out.
L'objecte cipher ha d'inicialitzar-se amb el mode que calgui, ENCRYPT_MODE o DECRYPT_MODE.
CipherInputStream es pot utilitzar de forma anàloga. En aquest cas, el stream de sortida podria ser un FileOutputStream:
FileInputStream fileIn = new FileInputStream(inputFilename);
CipherInputStream in = new CipherInputStream(fileIn, cipher);
FileOutputStream fileOut = new FileOutputStream(outputFilename);
Dades binàries en text
Les claus i la informació xifrada està en format binari. Si cal intercanviar-ho utilitzant un canal de text, es poden convertir utilitzant Base64. A Java, tenim java.util.Base64
.
byte[] binary1 = ...;
String string = Base64.getEncoder().encodeToString(binary1);
byte[] binary2 = Base64.getDecoder().decode(string);
// binary1 i binary2 són iguals
Resums, signatures i certificats
Resums de missatges
Els resums s'implementen utilitzant la classe java.security.MessageDigest
, que permet generar un resum de dades. El resum es pot fer segur utilitzant javax.crypto.Mac
. També es pot realitzar l'operació amb Streams gràcies a java.security.DigestInputStream i java.security.DigestOutputStream
.
Alguns algorismes típics de resums: MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512.
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
byte[] resum = messageDigest.digest(text.getBytes());
Resums segurs
Un Message Authentication Code (javax.crypto.Mac
) és un resum xifrat amb una clau secreta compartida. Només es pot verificar si tens aquesta clau. Podem generar-lo així:
Mac mac = Mac.getInstance(algorithm);
mac.init(key); // la clau secreta
byte[] macBytes = mac.doFinal(text.getBytes());
Un algorisme podria ser HmacSHA256 (HMAC: Hash-based MAC).
Key Derivation Functions
Amb una KDF, podem generar una clau més llarga que un passowrd, per exemple. Aquí tenim un exemple utilitzant l'algorisme PBKDF2WithHmacSHA256.
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec ks = new PBEKeySpec(password, salt, iterationCount, keyLength);
SecretKey rawSecret = f.generateSecret(ks); // secret genèric
SecretKey aesSecret = new SecretKeySpec(s.getEncoded(), "AES"); // secret AES
Els paràmetres del KeySpec són:
- password: un char[] amb la contrasenya.
- salt: una salt per a randomitzar el hash.
- iterationCount: nombre d'iteracions per a generar el hash.
- keyLength: longitud de la clau a generar.
Signatures digitals
Una signatura digital equival a fer un resum i xifrar-lo amb una clau privada. El receptor podria desxifrar-lo amb la pública, i comparar-lo amb un resum que faci de les dades (en pla) rebudes.
Els algorismes són variats, per exemple, SHA256withRSA indica que el resum es fa amb SHA256 i el xifratge amb RSA. Per tant, les claus utilitzades han de ser RSA. Per a signar una entrada (array de bytes):
Signature sign = Signature.getInstance(algorithm);
sign.initSign(privateKey);
sign.update(input);
byte[] signatura = sign.sign();
Per verificar-la:
Signature sign = Signature.getInstance(algorithm);
sign.initVerify(publicKey);
sign.update(input);
boolean correcte = sign.verify(signatura);
Certificats
Els certificats (java.security.cert.Certificate
) més habituals són de tipus X.509, i indiquen una vinculació d'una identitat a una clau pública, garantida per una altra entitat autoritzada. Inclouen:
- data d'inici i fi
- versió (3 actualment)
- número de sèrie (únic per proveïdor)
- el DN (distinguished name) de la CA emissora
- el DN del subjecte del certificat
Els DN (Distinguished Names) conté una sèrie de camps (CN, OU, O, L, S, C) que identifiquen tant l'emissor com el subjecte.
Habitualment, els trobem dins dels magatzems. Per gestionar-los podem utilitzar l'eina keytool
del JRE o bé programàticament amb la classe java.security.KeyStore
i els mètodes getKey(alias)
(clau privada) i getCertificate(alias)
(certificat on hi la clau pública).
Són necessaris als portals web amb seguretat habilitada HTTPS.
Comunicació SSL amb sòcols
Per tal que un socket es pugui comunicar amb el protocol segur SSL, cal crear objectes una mica diferents.
Al servidor:
ServerSocket serverSocket = new ServerSocket(PORT);
Es converteix en:
SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(PORT);
Opcionalment, si volem autenticar al client (ho veurem més endavant):
serverSocket.setNeedClientAuth(true);
Al client:
Socket clientSocket = new Socket(HOST, PORT);
Es converteix en:
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) factory.createSocket(HOST, PORT);
A més, cal configurar les claus privada i pública correctament. I això depèn de si volem o no autoritzar la clau pública dels clients al servidor.
Tenim dos tipus de magatzems necessaris:
- Els keystores serveixen per identificar-nos
- Els truststores serveixen per autoritzar altres parts
La configuració es realitza mitjançant propietats de sistema a Java, amb aquesta instrucció:
System.setProperty("nomDeLaPropietat", "valor");
Sense autorització del client
Cal generar dos magatzems:
- serverKeystore.jks: clau pública/privada del servidor
- clientTruststore.jks: clau pública del servidor
keytool -genkey -alias srvAlias -keyalg RSA -keystore serverKeystore.jks -keysize 2048
keytool -export -keystore serverKeystore.jks -alias srvAlias -file server.crt
keytool -importcert -file server.crt -keystore clientTruststore.jks -alias srvAlias
Configuració al servidor per a identificar-se:
javax.net.ssl.keyStore=serverKeystore.jks
javax.net.ssl.keyStorePassword=yourpassword
Configuració al client per a acceptar al servidor (només si el servidor no està autoritzat per una CA Authority):
javax.net.ssl.trustStore=clientTrustore.jks
javax.net.ssl.trustStorePassword=yourpassword
Amb autorització del client
Als les configuracions anteriors, cal afegir:
- clientKeystore.jks: clau pública/privada del client
- serverTruststore.jks: clau pública del client
keytool -genkey -keyalg RSA -alias cltAlias -keystore clientKeystore.jks -keysize 2048
keytool -export -keystore clientKeystore.jks -alias cltAlias -file cliente.crt
keytool -importcert -file cliente.crt -keystore serverTruststore.jks -alias cltAlias
Configuració al servidor per a acceptar el client:
javax.net.ssl.trustStore=serverTruststore.jks
javax.net.ssl.trustStorePassword=yourpassword
Configuració al client per a identificar-se:
javax.net.ssl.keyStore=clientKeystore.jks
javax.net.ssl.keyStorePassword=yourpassword
Gestió de claus
Generació i destrucció
Consells sobre generació:
- Per a xifrat simètric, utilitzar AES amb almenys 128 bits, millor 256.
- Utilitzar modes autenticats si és possible: GCM, CCM. Si no, CTR o CBC. ECB hauria d'evitar-se.
- Per a xifrat asimètric, utilitzar criptografia de corba el·líptica (ECC) com Curve25519. Si no, utilitzar RSA d'almenys 2048 bits.
- Utilitzar generadors aleatoris segurs. Millor CSPRNG que PRNG. A Java, preferir sempre SecureRandom sobre Random.
És important fer que les claus tinguin una durada limitada (rotar-les): desxifrar i rexifrar. Cal fer rotació de les claus si:
- Si se sap (o se sospita) que la clau anterior ha estat compromesa.
- Quan passi un cert temps predeterminat.
- Quan s'hagi xifrat una certa quantitat de dades.
- Si ha canviat la seguretat associada a un algorisme (nous atacs).
Emmagatzematge
Les aplicacions gestionen diferents tipus de secrets: credencials, claus de xifratge, claus privades de certificats, claus d'API, dades sensibles, etc.
Els secrets s'han de xifrar en repòs i en trànsit. Les claus per xifrar secrets s'anomenen Data Encryption Keys (DEK). Aquestes claus també s'han de protegir, i el que es fa és xifrar-les utilitzant el que es diu Key Encryption Key (KEY) o "Master Key". Aquest esquema permet modificar la KEK mantenint les DEK, i per tant, sense requerir el rexifratge de les dades.
Consells amb les claus:
- Si és possible, no emmagatzemar mai la KEK. Per exemple, demanar-la interactivament quan calgui utilitzant una Key Derivation Function (KDF).
- Si és factible, utilitzar un HSM (Hardware Security Module).
- Millor no mantenir-les en memòria i en pla.
- Mai guardar-les al codi ni al git.
- Les DEK i les KEK s'han d'emmagatzemar en llocs diferents. Per exemple, la base de dades i el sistema d'arxius, o en màquines diferents.
- Si van a un fitxer, protegir els arxius amb permisos restrictius.
- Fer key stretching des d'una contrasenya per generar la KEK. Per exemple, amb la funció de derivació de claus PBKDF2.
El següent diagrama mostra una KEK/DEK amb vectors d'inicialització (KIV/DIV) i gestionades mitjançant una KDF, que permet generar la KEK a partir d'un password.
KeyStores
Un magatzem de claus, o keystore, pot tenir un alias, i pot contenir:
- Claus: pot ser una clau (asimètrica o simètrica). Si es tracta d'una asimètrica, pot contenir una cadena de certificats.
- Certificats: una clau pública, habitualment l'arrel de CAs de confiança.
Les keystores poden tenir diferents formats (JKS, JCEKS, PKCS12, PKCS11, DKS). Els més utilitzats són:
- JKS (Java Key Store): format propietari de Java. Històricament, el més utilitzat. Extensió jks.
- PKCS#12: format standard. Format recomanat. Extensió p12 o pfx.
Java té una eina anomenada keytool per gestionar magatzems de claus. A continuació es mostren algunes comandes habituals.
Comenda per llistar els continguts d'una keystore:
$ keytool -list -v -keystore keystore.jks
Comanda per generar un keystore amb una parella de claus:
$ keytool -genkey -alias mydomain -keyalg RSA -keystore keystore.jks -keysize 2048
Comanda per exportar un certificat d'una keystore:
$ keytool -export -alias mydomain -file mydomain.crt -keystore keystore.jks
Comanda per importar un certificat a una keystore:
$ keytool -importcert -file mydomain.crt -keystore keystore.jks -alias mydomain
Certificats digitals
Un certificat digital és un document electrònic que actua com una mena d'identitat digital per a persones, serveis o dispositius. La seva funció principal és vincular una clau pública amb una identitat concreta de manera fiable, gràcies a la signatura digital d'una Autoritat de Certificació (CA).
Les CAs no signen directament tots els certificats digitals, sinó que utilitzen els certificats intermedis, que serveixen de pont entre els certificats arrel (CA) i els finals.
Aquestes són funcions que permeten els certificats:
- Autenticació: Verifiquen la identitat de l'entitat amb qui es comunica el sistema. Per exemple, quan accedeixes a un lloc web segur (HTTPS), el certificat del servidor t'assegura que estàs parlant amb el lloc web legítim.
- Integritat: Gràcies a la signatura digital, qualsevol modificació del certificat un cop emès pot ser detectada.
- Xifratge: Faciliten l'intercanvi segur de claus per establir connexions xifrades (com en TLS/SSL).
- No repudiació: Proporcionen una evidència que l'entitat posseïa la clau privada corresponent en el moment de la signatura.
L'especificació més comuna dels certificats és la X.509, que inclou aquests camps:
- Identificació del subjecte: El nom o l'entitat a la qual es fa referència (p. ex., domini web, nom de persona o organització).
- Clau pública: La clau que s'utilitza per xifrar dades o verificar signatures. Identificació de l'emissor (CA): Informació sobre l'autoritat de certificació que ha emès el certificat.
- Període de validesa: La data d'inici i de caducitat del certificat.
- Número de sèrie: Un identificador únic per al certificat.
- Algoritme de signatura: L'algoritme utilitzat per generar la signatura digital.
Els certificats X.509 segueixen un estàndard que defineix què han de contenir i com s'organitzen, però per a poder ser emmagatzemats, transmesos o usats en diferents entorns, cal "encapsular-los" en un format concret. Aquí tenim les principals maneres de codificar i empaquetar-los:
-
DER (Distinguished Encoding Rules): és una codificació binària per als certificats X.509. Aquest format és compacte i està dissenyat perquè l'estructura del certificat es pugui interpretar de manera inequívoca per les màquines.
-
PEM (Privacy-Enhanced Mail): és essencialment la codificació DER convertida a text mitjançant Base64. Els certificats en format PEM tenen delimitadors clars, com ara:
-----BEGIN CERTIFICATE-----
(dades codificades en Base64)
-----END CERTIFICATE-----
Aquest format és molt popular perquè és fàcil d'utilitzar en entorns de text i és compatible amb molts programes.
-
PKCS#7: és un format que permet empaquetar una o diverses certificats (per exemple, tot el camí de certificació) en un sol fitxer. Aquest format és molt utilitzat en entorns on es necessita transmetre tota la cadena de certificació, com en certs sistemes de correu electrònic segur (S/MIME).
-
PKCS#12: aquest format permet empaquetar no només els certificats X.509, sinó també la seva clau privada associada. Els fitxers PKCS#12 (habitualment amb extensions .pfx o .p12) solen estar protegits amb una contrasenya per garantir la seguretat de la clau privada.
Seguretat
Resultats d'aprenentatge (compartit amb Criptografia):
- Protegeix les aplicacions i les dades definint i aplicant criteris de seguretat en l’accés, emmagatzematge i transmissió de la informació.
Parlarem de la seguretat lògica (software) i activa (preventiva) associats al desenvolupament de programari.
Un sistema es pot considerar segur si ens cuidem dels següents aspectes, de més a menys significatius:
- Disponibilitat: els usuaris poden accedir a la informació quan ho necessiten.
- Confidencialitat: la informació és accessible només per aquells autoritzats a tenir accés.
- Integritat: mantenir les dades lliures de modificacions no autoritzades.
- Autenticació: verificació de la identitat.
- No repudi: ni l'emissor ni el receptor poden negar ser part en la comunicació que es produeix.
Dins de la programació, i en referència a la seguretat, parlarem dels següents aspectes:
- Control d'accés: registre, autenticació i autorització d'usuaris.
- Disseny segur de programari per evitar vulnerabilitats.
Referències
Seguretat:
- Security Features in Java SE (The Java Tutorials)
- Secure Coding Guidelines for Java SE
- Security by design (Wikipedia)
- Application Security (Wikipedia)
- SEI CERT Oracle Coding Standard for Java
- Top 10 Secure Coding Practices
- Secure Programming for Linux and Unix (Java specific)
- How to Learn Penetration Testing: A Beginners Tutorial
- OWASP Proactive Controls
- OWASP API Security Project
- Role-based access control (Wikipedia)
- Please, stop using local storage
- HTTP headers for the responsible developer
- How to Use Local Storage with JavaScript
- Advanced API Security (Llibre)
- How secure is Java compared to other languages?
- The Rule Of 2
- Programming With Assertions
Autenticació / autorització:
- HTTP Authentication
- Session vs Token Based Authentication
- The Web Authentication Guide Cheatsheet
- JWT, JWS and JWE for not so dummies!
- Java Authentication with JSON Web Tokens (jjwt)
- Tutorial: Create and Verify JWTs in Java
- Refresh Tokens: When to Use Them and How They Interact with JWTs
- Attacking JWT authentication
- Token based authentication made easy
- Getting Token Authentication Right in a Stateless Single Page Application
- Stateless Sessions for Stateful Minds: JWTs Explained and How You Can Make The Switch
- Web Security for SPAs
- Webauthn guide
- An Introduction to OAuth 2
- OAuth 2.0 clients in Java programming, Part 1, Part 2 i Part 3
- LDAP Security
- REST Security Cheat Sheet (OWASP)
- Password Storage Cheat Sheet (OWASP)
- Cryptographic Storage Cheat Sheet (OWASP)
- REST API Security Essentials
Control d'accés
El control d'accés inclou les activitats de registre, autenticació i autorització d'usuaris.
Registre
El registre d'usuaris té associat l'emmagatzematge d'aquella informació necessària per poder autenticar-los posteriorment. És important evitar deixar la informació en clar en fitxers o bases de dades, per estalviar-nos problemes de seguretat. També, evitar encriptar els passwords.
Un esquema habitual és l'ús de resums o hash. Si guardem el hash a la BBDD, no sabrem quin és el password, però podem comparar el que entra l'usuari amb el hash guardat, i dir si és el mateix.
Això només té un problema: hi ha taules preconstruïdes per a cercar les correspondències entre hash i password. Això ens obliga a afegir un string random (salt) al costat del password, i llavors el hash de tot plegat no és sempre el mateix per al mateix password. Aquest salt no ha de ser privat, compleix l'objectiu de fer inútils les tàctiques habituals per esbrinar passwords, i per tant es pot guardar en clar a la BBDD.
A l'hora de fer l'autenticació només haurem de fer la comparació entre el hash emmagatzemat i el calculat:
hash(salt + password1) és igual a hash(salt + password2)?
Una tècnica per a generar resums més segurs és el key stretching, que fa que la seva generació sigui lenta per a fer més difícil un atac de força bruta. Els algorismes KDF (Key Derivation Function) són un exemple.
Autenticació
L'autenticació implica, habitualment, recollir la identificació de l'usuari per tal de comprovar la seva autenticitat.
Un cop tenim l'usuari autenticat, aquest pot rebre un identificador generat pel servidor i que el client haurà de fer arribar cada petició al servidor per tal de confirmar que està autenticat.
Les aplicacions client / servidor es poden diferenciar en dos tipus: stateful i stateless: amb i sense estat. Això es refereix al fet que el servidor emmagatzemi o no dades associades a l'usuari autenticat, el que s'anomena sessió.
- Stateful: amb sessió i dades emmagatzemades al servidor. L'identificador generat és el de la sessió. El servidor passa un ID al client, que utilitza cada cop que es comunica amb el servidor. El servidor l'utilitza per obtenir les dades que té associades.
- Stateless: sense sessió i dades emmagatzemades al client. L'identificador generat es diu token. Pot ser simplement un ID aleatori generat, però habitualment contenen informació associada que ha estat signada criptogràficament.
Identificador al client
A les aplicacions web, si un client està autenticat cal que li faci saber al servidor mitjançant algun tipus d'identificador secret. El client podria ser un navegador, si és una aplicació web d'usuari, o una aplicació client.
Si el client és un navegador, hi ha bàsicament dos esquemes per guardar aquest identificador al client: cookies i web storage.
- Les cookies són part del protocol HTTP. Permeten guardar galetes nom/valor mitjançant una capçalera "Set-Cookie" des del servidor (resposta), i informen el servidor de les galetes actuals mitjançant una capçalera "Cookie" des del navegador.
- El web storage és un mecanisme activable des del client exclusivament, mitjançant scripting. Tenim dos objectes, sessionStorage i localStorage, que permeten accions del tipus setItem/getItem sobre parelles nom/valor. No és un mecanisme que directament substitueixi les cookies, tot i ser semblants.
Aquestes dues tecnologies podrien emmagatzemar identificacions per accedir a aplicacions. Les cookies envien la informació directament al servidor, mentre el web storage permet gestionar la informació al client, exclusivament.
Si es tracta d'una aplicació client, aquesta informació la pot guardar el programari corresponent, i enviar-la quan calgui al servidor.
En el cas que l'identificador no estigui xifrat, és important que no sigui fàcilment deduïble per evitar que es puguin construir maliciosament (ID aleatori i llarg). JWT proporciona l'opció de xifrar la informació d'accés i autorització.
Enviament de l'identificador
A continuació, es comenten alguns possibles mètodes per a enviar l'identificador al servidor.
-
HTTP Basic Authentication utilitza una capçalera del tipus:
Authorization: Basic base64(username:password)
-
Les cookies són el mètode més clàssic, i permeten dos headers especials, un del servidor:
Set-Cookie: sessionId=shakiaNg0Leechiequaifuo6Hoochoh; path=/; Secure; HttpOnly; SameSite
i un altre des del client:
Cookie: sessionId=shakiaNg0Leechiequaifuo6Hoochoh
-
Els tokens (bearer) es passen utilitzant una capçalera:
Authorization: Bearer ujoomieHe2ZahC5b
Els tokens solen tenir un límit de validesa, i s'utilitzen sovint amb aplicacions stateless.
-
Les firmes (signatures) signen i envien les dades significatives de la petició en format formulari. Per exemple: API AWS.
-
Els certificats de client TLS realitzen un handshake abans de cap petició HTTP.
Factors combinats
L'autenticació es pot fer a partir d'alguna cosa que l'usuari sap, té o és. Podem tenir un sol factor d'autenticació, o combinar-los. És habitual tenir un doble factor d'autenticació en serveis més segurs.
Un segon factor habitual és el One-Time Password (OTP). Es poden basar en sincronització de temps o algorismes matemàtics que generen cadenes. Hi ha dues implementacions: HOTP i TOTP. La diferència és què comparteixen per generar la contrasenya: un comptador o el temps (Google Authenticator).
- El servidor crea una clau secreta per a l'usuari, i la comparteix amb un codi QR sobre una sessió segura (HTTPS).
- Les dues parts generaran l'OTP utilitzant un secure hash (tipus HMAC), i el servidor haurà de validar si és l'esperat.
La generació es fa amb aquesta fòrmula:
- hash (shared secret + counter) = HOTP
- hash (shared secret + time) = TOTP
Autorització
Un cop l'usuari ha estat autenticat, hi ha un nombre de permisos que se li assignen en funció del seu rol dins de l'aplicació. Hi ha diferents formes d'assignar-los:
- Nivell: els usuaris i les tasques tenen nivells, un usuari pot fer les tasques amb nivell igual o menor al seu.
- Usuari: es fan parelles usuari-tasca (many2many)
- Grup: un usuari té un grup, es fan parelles grup-tasca
- Responsabilitat: un usuari pot tenir diversos grups
Un cop assignats els permisos, és important fer-los efectius en cadascuna de les interaccions de l'usuari amb el sistema. Això pot fer-se tant stateless (exemple: autoritzacions dins de JWT) com stateful (emmagatzematge a la sessió del servidor).
Autenticació amb sessió
Autenticació amb token
Disseny segur
- Criteris de disseny
- Pràctiques de codificació
- Pràctiques a Java
- Eines d'anàlisi
- Top 10 de controls proactius
- Top 10 d'atacs
La seguretat ha de ser una preocupació, no una funcionalitat.
Criteris de disseny
Aquesta és una llista de possibles criteris a tenir en compte per a dissenyar codi segur:
- El menor privilegi: una entitat només ha de tenir el conjunt necessari de permisos per realitzar les accions per a les quals estiguin autoritzades, i cap altre més.
- Fail-Safe per defecte: el nivell d’accés predeterminat d’un usuari a qualsevol recurs del sistema hauria de ser "denegat" a menys que se'ls concedís un "permís" explícitament.
- Economia del mecanisme: el disseny ha de ser el més simple possible. Això els fa més fàcils d'inspeccionar i confiar.
- Mediació completa: un sistema ha de validar els drets d’accés a tots i cadascun dels seus recursos.
- Disseny obert: els sistemes s'han de construir de forma oberta, sense secrets ni algorismes confidencials.
- Separació de privilegis: la concessió de permisos a una entitat ha de basar-se en múltiples condicions, no només una.
- Mecanisme menys comú: qualsevol cosa que es comparteixi entre diferents components pot ser una via de comunicació i un potencial forat de seguretat, i per tant s'han de compartir les dades mínimes possibles.
- Acceptabilitat psicològica: els mecanismes de seguretat no haurien de fer més difícil l'accés al recurs que si no hi fossin.
- Responsabilitat: el sistema ha de registrar qui és responsable d'utilitzar un privilegi. Si hi ha abús, podrem identificar el responsable.
Pràctiques de codificació
Aspectes tècnics que potencien la seguretat del codi:
- Immutabilitat: ens podem estalviar problemes associats a la integritat i disponibilitat de les dades.
- Disseny fail-fast per contractes: establint clarament quines són les precondicions i postcondicions perquè quelcom funcioni correctament.
- Validació: validem l'origen, la mida, el context, la sintaxi i semàntica de les dades que interactuen amb el sistema. Hi ha llibreries que ajuden a aquesta tasca, com la Commons Validator.
Pràctiques a Java
- Utilitza correctament els modificadors d'accés i redueix al màxim l'acoblament (API mínima).
- Evita serialització, reflection i introspection.
- No exposis credencials o informació personal, ni l'emmagatzemis al codi font o a arxius de recursos: utilitza l'entorn.
- Utilitza llibreries conegudes i testades, segueix les vulnerabilitats de dependències de tercers i actualitza a l'última versió.
- Utilitza sempre prepared statements (evita SQL injection).
- No mostris informació de la implementació als missatges d'error.
- Controla que l'entrada al sistema no causi ús desproporcionat de CPU, memòria i espai de disc.
- Allibera els recursos sempre: fitxers, memòria, etc.
- Comprova que no es produeixen overflows a tipus primitius, com integer (pots utilitzar
addExact
,multiplyExact
,decrementExact
de java.lang.Math).
Eines d'anàlisi
Tenim eines dinàmiques (que executen el codi) i estàtiques (analitzen el codi sense executar-lo).
Dinàmic:
- Integration and unit testing: cal dissenyar proves d'integració i unitàries al codi. A Java, tenim JUnit.
- Code coverage: eines que serveixen per detectar quin codi s'ha executat o no, i decidir si hem d'afegir test cases. Tenim una eina de coverage a Eclipse (Java), per exemple.
Estàtic:
- Static code analysis: permet fer anàlisi de la qualitat del codi que hem escrit, amb la detecció dels errors semàntics més habituals del llenguatge. A Java, podem utilitzar SpotBugs i les seves dues extensions, fb-contrib i find-sec-bugs.
Top 10 de controls proactius
Segons OWASP 2024, aquest és el TOP 10 dels controls que hauria de fer un desenvolupador:
- Implementar control d'accés. Cal permetre o denegar l'accés de forma granular i per a totes les peticions. Criteris: denegar per defecte, el privilegi menor, evitant rols hardcoded, i registrant els esdeveniments.
- *Utilitzar criptografia per a protegir les dades. Mai s'han de transmetre ni emmagatzemar dades sensibles en format pla. Cal utilitzar protocols coneguts amb pràctiques implementades a llibreries que estiguin al dia.
- Validar totes les entrades i gestionar excepcions. Fa el teu codi més segur i confiable. A més, els errors poden permetre detectar intents d'atac. Fes registre, i si mostres alguna cosa a l'usuari, que no contingui dades crítiques.
- Gestionar la seguretat des del començament. Integrar consideracions de seguretat en totes les fases del desenvolupament del programari per evitar la introducció de vulnerabilitats des del principi.
- Configurar la seguretat per defecte. Assegurar que el programari es desplega amb configuracions de seguretat robustes, minimitzant els riscos per defecte.
- Mantenir els components segurs. Actualitzar i gestionar correctament les llibreries i components utilitzats per evitar vulnerabilitats derivades de versions obsoletes o insegures.
- Identitats digitals segures. Hem de verificar la identitat de l'usuari (autenticació), i potser mantenir el seu estat (gestió de la sessió). Ho podem fer amb contrasenyes, amb múltiples factors i amb criptografia.
- Aprofitar les funcionalitats segures del navegador. Utilitzar les característiques integrades en els navegadors, com ara controls de contingut i polítiques de seguretat, per reforçar la protecció del costat client.
- Implementar el registre i monitoratge de la seguretat. Utilitzar sistemes de detecció d'intrusos, fer anàlisi forensic i implementar regulacions normatives.
- Refusar les sol.licituds falses de servidor (SSRF). Mitigar els riscos associats amb la capacitat d’un atacant per forjar peticions des del servidor, protegint així el sistema de possibles abusos.
Top 10 d'atacs
Segons el top 10 d'OWASP al 2021:
- Pèrdua del control d'accés: Les restriccions sobre el que els usuaris autenticats poden fer no s'apliquen correctament. Els atacants poden explotar aquests defectes per accedir, de forma no autoritzada, a funcionalitats i / o dades, comptes d'altres usuaris, veure arxius sensibles, modificar dades, canviar drets d'accés i permisos, etc.
- Exposició de dades sensibles: Moltes aplicacions web i APIs no protegeixen adequadament dades sensibles, com ara informació financera, de salut o Informació Personalment Identificable (PII). Els atacants poden robar o modificar aquestes dades protegides inadequadament per dur a terme fraus amb targetes de crèdit, robatoris d'identitat o altres delictes. Les dades sensibles requereixen mètodes de protecció addicionals, com el xifrat en emmagatzematge i trànsit.
- Injecció: Les falles d'injecció, com SQL, NoSQL, US o LDAP ocorren quan s'envien dades no fiables a un intèrpret, com a part d'un comando o consulta. Les dades nocives de l'atacant poden enganyar a l'intèrpret perquè executi ordres involuntaris o accedeixi a les dades sense la deguda autorització.
- Disseny insegur: la manca d’un disseny que integri principis de seguretat des del començament deixa l’aplicació més vulnerable a futurs atacs.
- Configuració incorrecta de seguretat: La configuració de seguretat incorrecta és un problema molt comú i es deu en part a establir la configuració manualment, ad hoc o per omissió (o directament per la falta de configuració). Són exemples: S3 buckets oberts, capçaleres HTTP mal configurades, missatges d'error amb contingut sensible, manca de pegats i actualitzacions, frameworks, dependències i components desactualitzats, etc.
- Utilització de components amb vulnerabilitats: Els components com biblioteques, frameworks i altres mòduls s'executen amb els mateixos privilegis que l'aplicació. Si s'explota un component vulnerable, l'atac pot provocar una pèrdua de dades o prendre el control del servidor. Les aplicacions i API que utilitzen components amb vulnerabilitats conegudes poden afeblir les defenses de les aplicacions i permetre diversos atacs i impactes.
- Trencament de l'autenticació: Les funcions de l'aplicació relacionades a autenticació i gestió de sessions són implementades incorrectament, permetent als atacants comprometre usuaris i contrasenyes, token de sessions, o explotar altres falles d'implementació per assumir la identitat d'altres usuaris (temporal o permanentment).
- Software and Data Integrity Failures: L’absència de mecanismes que verifiquin la integritat del codi i de les dades pot conduir a manipulacions malicioses.
- Registre i monitorització insuficients: El registre i monitorització insuficient, al costat de la manca de resposta davant incidents permeten als atacants mantenir l'atac en el temps, pivotear a altres sistemes i manipular, extreure o destruir dades. Els estudis mostren que el temps de detecció d'una bretxa de seguretat és major a 200 dies, sent típicament detectat per tercers en lloc de per processos interns.
- Server-Side Request Forgery (SSRF): aquesta vulnerabilitat permet a un atacant forjar peticions des del servidor cap a altres sistemes, exposant serveis interns o externs.
Tecnologies
Oauth
OAuth és un protocol web d'autorització per concedir als llocs web l'accés a algunes de les vostres dades personals o drets d'accés a algun sistema.
L'objectiu és obtenir un token d'accés per a accedir a un recurs protegit. Hi ha quatre modalitats:
- Propietari del recurs (resource owner): l'usuari que autoritza una aplicació a accedir al seu compte.
- Client: l'aplicació que vol accedir al compte d'usuari.
- Servidor de recursos (resource server): conté els comptes d'usuari.
- Servidor d'autorització (authorization server): verifica la identitat i emet els tokens d'accés a l'aplicació.
Aquests dos servidors se solen anomenar "API del servei".
Hi ha dos conceptes relacionats amb OAuth:
- El registre de l'aplicació: abans d'utilitzar OAuth, cal que l'aplicació es registri al servei indicant el seu nom, l'adreça web i la redirecció URI, on el servei redireccionarà després d'autoritzar l'aplicació i on l'aplicació gestiona els tokens d'accés. El registre genera un ID de client i un secret, utilitzat per autenticar l'aplicació a l'API.
- La concessió de l'autorització: se soporten 4 tipus a OAuth 2:
- Codi d'autorització: el més comú, utilitzat per aplicacions servidor.
- Implícit: utilitzat per aplicacions mòbils o web (dispositiu d'usuari).
- Credencials de contrasenya del propietari del recurs: utilitzat per aplicacions fiables.
- Credencials del client: utilitzat amb API d'aplicacions.
JSON Web Token (JWT)
JWT ens permet fer autorització. És un estàndar obert basat en JSON que permet crear tokens d'accés per a propagar identitat i afirmacions (claims). El token és compacte i pot ser enviat en l'entorn web perquè es pugui emmagatzemar al client. El token està signat pel servidor (amb una clau privada), per la qual cosa tant el servidor com el client poden verificar que és legítim.
Les afirmacions estàndar són:
- iss: issuer
- sub: subject
- aud: audience
- exp: expiration time
- nbf: not before
- iat: issued at
- jti: JWT ID
L'autenticació basada en token es pot utilitzar per habilitar una arquitectura stateless, però també es pot utilitzar en arquitectures stateful. Per exemple, un JWT pot contenir totes les dades de sessió necessàries, codificades directament al token, en aquest cas suporta una arquitectura stateless. JWT també es pot utilitzar per emmagatzemar una referència o ID per a la sessió; en aquest cas, les dades de sessió s’ha d’emmagatzemar del costat del servidor, fent que l’arquitectura sigui stateful.
Un esquema de funcionament habitual és el d'access token/refresh token:
- Access token: no passa pel servidor d'autenticació, es tracta només criptogràficament. Duren poc (minuts).
- Refresh token: passa pel servidor d'autenticació. Duren més, i es poden revocar.
Webauthn
Webauthn (Web Authentication) és un estàndar web (W3C) especificat per la FIDO Alliance que defineix una interfície per autenticar usuaris a aplicacions web i serveis mitjançant criptografia de clau pública. En el client, es pot implementar de diverses formes: purament en software, o bé mitjançant un gadget hardware, com per exemple un dispositiu USB. També Android està certificat.
El procediment té dues parts:
- El registre: el client envia una clau pública al servidor, que la registra.
- L'autenticació: el servidor demana al client que signi unes dades, i verifica que es poden desxifrar amb la clau pública que té registrada.
Registre de credencials
Autenticació
Interfícies
Disseny i implementació
Resultats d'aprenentatge:
- Genera interfícies gràfiques d’usuari mitjançant editors visuals fent servir les funcionalitats de l’editor i adaptant el codi generat.
- Genera interfícies gràfiques d’usuari basades en XML fent servir eines específiques i adaptant el document XML generat.
- Crea components visuals valorant i emprant eines específiques.
Referències
- JavaScript modern
- JavaScript, MDN
- Introduction to web APIs, MDN
- Go Make Things
- The Modern JavaScript Tutorial
- Client-Side Web Development
- JavaScript and React Patterns
- Web Applications 101
- https://node.green/
- React Fiber Architecture
Aplicacions web
Una aplicació web és un programari que implica un client i un servidor que es comuniquen utilitzant la xarxa i el seu protocol HTTP.
- El client és un navegador web, que sap com renderitzar documents basats en els estàndards web HTML (estructura), CSS (estil) i JavaScript (lògica).
- El servidor permet rebre peticions HTTP de diferents tipus (GET, POST, PUT, DELETE...) per a retornar, crear, modificar o esborrar continguts.
Hi ha diferents models d'aplicació web que es distingeixen en funció de qui, com i quan genera els documents o continguts que renderitzarà el navegador per a l'usuari.
Història
Inicialment, els continguts eren només estàtics. Hi ha servidors web que serveixen HTML i CSS amb HTTP GET. L'adreça del navegador és un HTTP GET cap el servidor, que serveix continguts estàtics. Aquests continguts els ha creat prèviament un dissenyador utilitzant eines de disseny.
L'aparició de PHP revoluciona la web. Es tracta d'un llenguatge que permet rebre peticions HTTP POST, accedir a bases de dades i generar documents HTML de forma dinàmica. Per tant, els documents els genera el servidor dinàmicament en funció de les peticions del navegador utilitzant motors de plantilles. Se'n diuen Multi-Page Applications (MPA), i van apareixen nous frameworks (Servlets, ASP.NET, Laravel, Django, Spring).
També el navegador evoluciona, amb JavaScript, que permet manipular el DOM i fer interaccions dinàmiques, així com Ajax, que permet fer peticions al servidor sense recarregar la pàgina. Els navegadors són cada cop més compatibles, i es desenvolupa un motor JavaScript en el servidor, Node.js. Podem comunicar-nos amb el servidor amb altres tipus de continguts, com per exemple JSON. També tenim JQuery, una llibreria que facilita la manipulació del DOM.
És el preàmbul de les Single-Page Applications (SPA).
Single-Page Application
Una SPA encapsula la majoria de la lògica de l'aplicació al client mitjançant JavaScript, i sap com generar els documents HTML i CSS. El navegador obté aquesta lògica mitjançant una petició de documents estàtics (HTML amb JavaScript), i esdevé una aplicació autònoma. De fet, intercepta el routing de la URL a l'adreça del navegador, que no produeix necessàriament comunicació amb el servidor.
Només accedirà a un servidor si necessita interactuar amb altres recursos externs dins d'una arquitectura centralitzada que comparteix amb altres usuaris o aplicacions. D'aquesta arquitectura se'n diu full-stack web, i té dues aplicacions: la del client (front-end) i la del servidor (back-end), comunicades amb un protocol sobre HTTP i un format de dades, com JSON.
Tenim diverses solucions al client i al servidor, que poden ser intercanviables si es respecta el protocol:
- Al client hi ha diverses llibreries o frameworks, com Angular, React o Vue.
- Al servidor es poden utilitzar frameworks MPA o bé de nous, com Node.js (Express.js).
Patrons de disseny
En parlarem dels patrons bàsics en els quals es basa el disseny d'interfícies.
Vista d'arbre
La vista d'arbre és un patró en el qual es basen moltes eines. La idea essencial és que hi ha una estructura en forma d'arbre de vistes (també nodes o elements). Cada vista pot contenir altres vistes (contenidors de vistes). Normalment, si una vista no es mostra tampoc ho fan les vistes contingudes.
- Sortida: les vistes són responsables de la seva visualització, i quan es muten es tornen a redibuixar.
- Entrada: les vistes tenen gestors d'entrades en funció de la interacció del teclat o del ratolí.
- Maquetació: l'arbre és responsable de la disposició de les vistes, generant el seu bounding box, que permet la seva maquetació en funció d'algorismes.
Listeners
Els listeners, també anomenats gestors d'esdeveniments, subscriptors o observadors, gestionen els esdeveniments d'entrada. Permeten assignar un gestor quan un esdeveniment concret passa.
Model-View
La separació d'interessos (Separation of Concerns: SoC) ens diu que és millor que àrees de diferents funcionalitats tinguin poc solapament. La primera separació amb sentit seria que la sortida la gestionin les vistes i l'entrada els listeners. Però encara ens faltaria afegir l'aplicació en sí.
Per a interpretar els patrons a continuació utilitzarem tres tipus d'interaccions:
- Comandes: són peticions per a realitzar canvis. No esperen cap resposta.
- Consultes (queries): són peticions per a obtenir valors de l'estat. No realitzen cap canvi.
- Notificacions: són notificacions d'esdeveniments que han passat.
En relació a comandes i consultes, és important comentar el concepte de Command-Query Separation. El que afirma és que cada mètode hauria de ser una ordre que realitzi una acció o una consulta que retorni dades al client, però no totes dues.
El criteris generals que segueixen tots els patrons són:
- El Model no coneix (no depèn de) cap altre component del patró.
- La View és l'únic component que fa referència a aspectes visuals.
MVC
El primer patró que es va utilitzar és MVC (Model-View-Controller).
Aquest patró utilitza Model, View i Controller.
- View: rep notificacions de canvis al Model i notifica el Controller d'esdeveniments de la UI.
- Controller: rep notificacions d'esdeveniments a la View i envia comandes al Model i la View.
- Model: notifica la View i rep consultes seves. També rep comandes i consultes del Controller.
El flux és:
- View (esdeveniment)
- Controller (comanda a Model)
- Model (esdeveniment)
- La View s'actualitza
MVP
Per tal de fer més testable per unitats cada part, apareix el patró MVP, on el Controller es substitueix pel Presenter. Aquest patró fa que totes les interaccions passin pel Presenter, i la View i el Model mai es comuniquen.
El flux és:
- View (esdeveniment)
- Presenter (comanda a Model)
- Model (esdeveniment)
- Presenter (comanda a View)
- La View s'actualitza
MVVM
Aquest patró és similar al MVP, però substitueix el Presenter pel ViewModel. Utilitza la idea de data binding, on els canvis de les dades es transmeten fins a la View a partir del Model mitjançant el ViewModel.
El flux és:
- View (commanda a ViewModel)
- ViewModel (comanda a Model)
- Model (esdeveniment)
- ViewModel (esdeveniment)
- La View s'actualitza
Construcció de la vista
A l'hora de construir l'arbre de vistes tenim dos paradigmes:
- Procedural: el codi indica, pas a pas, com arribar a l'estat desitjat de l'arbre de vistes.
- Declaratiu: el codi representa directament l'arbre de vistes en funció de l'estat actual.
El paradigma procedural consisteix en escriure la seqüència de passos necessària per a convertir l'arbre de vistes anterior al nou, reflectint el nou estat de l'aplicació.
En canvi, el paradigma declaratiu necessita un codi que directament reprodueixi l'arbre de vistes a partir de l'estat. Algunes eines permeten identificar quin subconjunt de l'arbre de vistes cal tornar a generar, sempre que puguem identificar quines vistes s'han vist afectades per la mutació de l'estat.
JavaScript i APIs
Per a poder desenvolupar al front-end, cal tenir un coneixement de JavaScript avançat basat en l'estàndard ECMAScript ES6 (2015) i que s'utilitza al front-end i a l'entorn de desenvolupament associat.
Veure la pàgina de JavaScript modern per a entendre les funcionalitats base del llenguatge.
Al marge del llenguatge, tenim una sèrie d'APIs que permeten interactuar amb el navegador anomenades Web API:
- Manipulació DOM (DOM API), que inclou, entre d'altres:
- Accés i control d'elements HTML
- Manipulació de dades de formularis
- Accés a la història del navegador
- Scripts en background (Web Workers API)
- Websockets i Server-sent events
- Obtenció de dades d'un servidor (Fetch API)
- Manipulació de gràfics (Canvas i WebGL) i interacció amb àudio i vídeo
- APIs del dispositiu, per exemple, geolocalització
- Emmagatzematge de dades al dispositiu (Web Storage API)
Cal dir que Node.js, un runtime JavaScript standalone, és principalment compatible amb l'especificació ES6 (veure node.green), però no amb les APIs del navegador.
Esdeveniments
Podem associar un esdeveniment del DOM a una crida d'una funció amb element.addEventListener(event, fn)
. Podem esborrar el listener amb element.removeEventListener(event, fn)
.
La funció listener pot tenir un paràmetre, l'esdeveniment. Aquest objecte té la propietat target
, que fa referència a l'objecte que origina l'esdeveniment, i type
, el tipus d'esdeveniment, entre d'altres.
Manipulació DOM
Alguns mètodes importants que permeten manipular el DOM:
document.querySelector(selector)
,document.querySelectorAll(selector)
. Versions clàssiques:getElementById(id)
,getElementsByTagName(name)
...document.createElement(tag)
,element.appendChild(child)
,element.removeChild(child)
,element.remove()
.
Fetch API
El mètode fetch(resource, options)
és la forma estàndard d'obtenir dades de forma asíncrona. Retorna una promesa.
Paradigma declaratiu
En la programació imperativa, el canvi està associat al flux d'execució i l'operador assignació. És una definició operacional.
La programació declarativa es basa en la definició conceptual, es defineixen relacions atemporals.
La programació reactiva es basa en el paradigma declaratiu: les nostres sortides es descriuen declarativament en funció de les nostres entrades. Això les permetrà reaccionar als canvis que es produiran a les entrades al llarg del temps.
Si utilitzem programació basada en esdeveniments, podem associar un listener als canvis (esdeveniments) produïts a les entrades. Llavors, es poden modificar les sortides. Reactivament, canviem el concepte per una relació "el seu valor depèn de" de les sortides cap a les entrades. Conceptualment, podem imaginar que una sortida és listener de totes les entrades de què depèn, i que actualitza el seu valor cada cop que es crida.
El funcionament reactiu executa un programa es produeix en dues fases:
- Inicialització: construir un graf dirigit de dependències
- Execució: els valors entren i produeixen canvis a les sortides És similar a com funciona una GUI: primer es construeixen els widgets i després el bucle d'esdeveniment els processa.
Gestió de l'estat
La UI (vista) és el resultat de dibuixar una representació de les dades. La gestió de l'estat inclou com representar aquestes dades i gestionar els seus canvis. La relació entre UI i estat és:
UI = fn(estat)
D'això se'n diu UI basada en l'estat. Per explicar com es produeixen els canvis que un usuari produeix sobre l'estat, cal introduir el concepte d'acció:
En aquest esquema es diu que l'estat és un observable, i que l'UI és un observador dels canvis que es produeixen en l'estat. Els observadors es subscriuen als observables, i seran notificats per ells quan hi hagi canvis.
Per tant, l'UI no canvia directament l'estat, li cal passar un missatge (acció) que encapsula el canvi d'estat. Un canvi d'estat fa que tots els seus observadors siguin notificats. L'UI és el principal subscriptor, i quan se li notifica d'un canvi d'estat (observables) torna a renderitzar-se per a reflectir-ho.
Efectes secundaris
Algunes operacions no tenen un efecte directe sobre la UI. Per exemple: obtenir dades d'un servidor, guardar-les, executar alguna tasca periòdica, etc. Aquestes són operacions externes no associades a l'UI, i les diem efectes secundaris o side effects.
Web reactiva
Les interfícies (o sortides) reaccionen als canvis que es produeixen a les seves dependències (o entrades). Són observadors. El paradigma reactiu ens permet oblidar-nos de com i quan es tornarà a redibuixar la UI. D'això s'encarrega la llibreria, que detecta els canvis a realitzar al DOM i els aplica.
La relació entre entrada i sortida és declarativa, i s'implementa de diferents formes segons la llibreria o framework. Habitualment tenim un cert estat (entrades) i un markup (sortides) que el referencia.
Les operacions bàsiques essencials d'un framework reactiu són:
- Definició d'estats (observables).
- Definició d'UI (observador) a partir dels estats (observables).
- Definició de side-effect handlers (observador).
- Modificació d'un estat a partir d'un esdeveniment UI o un side-effect.
- Creació d'un observable computat a partir d'altres observables (optimització).
Toolchain
L'entorn de desenvolupament dels frameworks JavaScript requereix d'un conjunt d'eines (toolchain) per a realitzar diferents funcions:
- Gestor de paquets: permeten instal.lar i utilitzar paquets de tercers tant en temps de desenvolupament com en temps d'execució. Exemples: npm, yarn.
- Bundler: permet desenvolupar modularment el codi i després optimitzar-lo per a carregar ràpidament en temps d'execució. Exemples: webpack, parcel, vite.
- Compiladors: permeten transformar el codi font (transpile/polyfill) des de JavaScript modern (cada cop menys necessari), Typescript o altres DSL (syntax sugar) a JavaScript que pot executar-se als navegadors. Exemples: babel.
- Linters: analitzador estàtic de codi que permet detectar errors. Exemple: eslint.
- Prettiers: formatadors de codi. Exemple: prettier.
Totes aquestes eines són integrables des de diferents editors de codi, com per exemple VSCode o Webstorm.
Fonaments React
React és un framework basat en la programació declarativa de components. Cada component declara com s'ha de renderitzar en funció de les propietats d'entrada i el seu estat:
(props, state) => view
La vista només es torna a renderitzar si canvien les propietats o l'estat.
Els components pare es comuniquen amb els fills passant propietats, i els fills poden generar esdeveniments cap als pares mitjançant callbacks.
El procés de renderitzar components es realitza sobre un DOM virtual, detectant quins canvis s'han produït a l'arbre de nodes respecte el DOM anterior. Només els canvis s'enviaran al DAM real, en un procés anomenat reconciliació.
Hi ha dues formes d'escriure de components:
- basats en classes (tradicional): s'utilitzen classes JavaScript que estenen React.Component amb mètodes del cicle de vida. Cal, almenys, definir el mètode render(), una funció pura que retorna el contingut a renderitzar.
- basats en funcions (modern): són funcions JavaScript amb un paràmetre, les propietats, que retornen el contingut a renderitzar. Utilitzen el concepte de hook, unes crides al framework que permeten gestionar l'estat o els efectes secundaris, per exemple.
Els components responen a tres esdeveniments:
- Muntatge (mount): quan el component es crea i s'afegeix al DOM.
- Actualització (update): quan el component s'actualitza en modificar-se una propietat o estat.
- Desmuntatge (unmount): quan el component s'esborrar del DOM.
El flux de React és el següent:
- Fase de renderitzat
- Es construeixen els components a muntar (1)
- Es renderitzen a VDOM els nous components i aquells que calgui actualitzar (1, 2)
- Fase de commit
- S'actualitza el DOM (real)
- El navegador pinta el DOM actualitzat
- Els components actualitzats i desmuntats (2, 3) fan el cleanup d'efectes
- Els components nous i actualitzats criden els efectes (1, 2)
Toolchain
El toolchain React és un conjunt d'eines que permeten desenvolupar aplicacions React. Inclou un servidor de desenvolupament, un compilador de JavaScript (Babel), un gestor de paquets (npm o yarn) i un empaquetador de mòduls (Webpack o Vite).
Després que React va deprecar el create react app, el toolchain oficial és Vite. Vite és un empaquetador de mòduls que permet un desenvolupament més ràpid i eficient. Utilitza una arquitectura basada en mòduls ES i ofereix una experiència de desenvolupament més ràpida i fluida.
Per crear una aplicació React amb Vite cal executar la comanda següent (segons es volgui JavaScript o TypeScript):
# React + JavaScript
$ npm create vite@latest my-app --template react`
# React + TypeScript
$ npm create vite@latest my-app --template react-ts
JSX i components
React utilitza una sintaxi estesa de JavaScript anomenada JSX. Un compilador del toolchain (Babel) s'encarrega de convertir-ho a JavaScript. Està basada en HTML, però permet codi JavaScript dins de claus {}.
Permet definir elements React, que són objectes creats amb React.createElement()
. Els elements poden ser de tipus HTML o bé definits pel desenvolupador.
Conceptualment, els components són funcions de JavaScript. Accepten entrades arbitràries (anomenades props) i retornen elements de React que descriuen el que hauria d'aparèixer a la pantalla. Aquestes propietats són de només lectura, i poden incloure callbacks per a gestionar esdeveniments.
Flux de dades
Les dades de React només viatgen des del pare cap els fills (one way data flow), i ho fan mitjançant les props. Les props són un objecte immutable, mentre els estats són mutables. Però com les props són immutables, els canvis de l'estat als fills no afecten els pares.
function Child({ propName }) {
return (<h1>Hello {propName}!</h1>);
}
function Parent() {
return (<Child propName={"React"} />);
}
function Child({ propName }: { propName: string }) {
return (<h1>Hello {propName}!</h1>);
}
function Parent() {
return (<Child propName={"React"} />);
}
Com que un fill no pot modificar l'estat d'un pare el que fem és aixecar l'estat (lifting state up). Això permet compartir-lo i accedir a aquells components que el necessiten.
El flux invers és possible utilitzant esdeveniments enviats des dels fills als pares.
function Child({ message, callBack }) {
return (
<button onClick={() => callBack("message from child!")}
>{message}</button>);
}
function Parent() {
const [message, setMessage] = useState('no message');
return (
<Child message={message}
callBack={propValue => setMessage(propValue)} />);
}
function Child({ message, callBack }: { message: string, callBack: () => void }) {
return (
<button onClick={() => callBack("message from child!")}
>{message}</button>);
}
function Parent() {
const [message, setMessage] = useState<string>('no message');
return (
<Child message={message}
callBack={propValue => setMessage(propValue)} />);
}
Hooks
React modern utilitza els components basats en funcions. Un component és una funció amb props d'entrada i el contingut a mostrar de sortida, i utilitza els hooks per a definir el seu comportament.
Els hooks permeten implementar les operacions de React:
- Definició d'estats amb useState.
- Definició d'UI a partir dels estats amb el JSX retornat.
- Definició de side-effect handlers amb useEffect.
- Modificació d'un estat amb setState.
- Creació d'un observable computat a partir d'altres observables amb useMemo.
React permet definir custom hooks per part de l'usuari. Bàsicament, es tracta de lògica amb estat reutilitzable on no hi ha JSX. S'implementen mitjançant una funció anomenada useXXX que pot contenir altres hooks i, habitualment, retornen un objecte o una tupla.
import { useState, useEffect } from 'react';
function useDelayedMessage(delay = 1000) {
const [message, setMessage] = useState("Waiting...");
useEffect(() => {
const timer = setTimeout(() => {
setMessage("Hello, world!");
}, delay);
// Cleanup the timer if the component unmounts or delay changes
return () => clearTimeout(timer);
}, [delay]);
return message;
}
import { useState, useEffect } from 'react';
function useDelayedMessage(delay: number = 1000): string {
const [message, setMessage] = useState<string>("Waiting...");
useEffect(() => {
const timer = setTimeout(() => {
setMessage("Hello, world!");
}, delay);
// Cleanup the timer if the component unmounts or delay changes
return () => clearTimeout(timer);
}, [delay]);
return message;
}
Disseny React
- 1 Descomposició
- 2 Versió estàtica
- 3 Estat mínim i suficient
- 4 Localització de l'estat
- 5 Flux invers de dades
- 6 Extreure lògica dels components
Imaginem que tenim una maqueta de la interfície. Els passos habituals per a dissenyar una aplicació React serien:
- Descompondre la interfície en una jerarquia de components.
- Construir una versió estàtica en React.
- Trobar la mínima representació possible de l'estat.
- Identificar on ha de situar-se l'estat dins de la jerarquia.
- Afegir flux invers de dades.
- Extreure lògica dels components.
1 Descomposició
Identifica les responsabilitats dins del disseny i separa-les en components.
2 Versió estàtica
Començar creant una versió no interactiva. Cal decidir quines props passen de dalt cap a baix de la jerarquia, però sense utilitzar estat. Es diu flux d'un sol sentit.
3 Estat mínim i suficient
La interfície d'usuari es fa interactiva gràcies a la possibilitat de modificar les dades del nostre model. Ho podem fer utilitzant estat: el mínim conjunt de dades que la nostra aplicació ha de recordar.
Identifica les dades que són estat:
- canvien al llarg del temps,
- no arriben per les props,
- no són computables a partir d'un altre estat o les props.
4 Localització de l'estat
Cal identificar quin component modifica l'estat i per tant li pertany. Es pot fer en tres passos:
- Identifica els components que utilitzen l'estat
- Trobar el pare comú més proper de tots dins la jerarquia
- Decideix on ha de localitzar-se. Habitualment és al pare comú, tot i que podria ser qualsevol ancestre d'aquest pare.
5 Flux invers de dades
Si volem que un component fill pugui modificar l'estat d'un component pare, cal que el pare passi un callback mitjançant les props que el fill pugui cridar per a realitzar el flux invers de dades.
6 Extreure lògica dels components
Els custom hooks són una forma d'extreure lògica dels components amb estat. Si ens trobem que utilitzem diversos hooks de React, amb diversos estats (useState) i possiblement effectes (useEffect) o altres, es pot simplificar i encapsular en un custom hook. Això simplificarà el codi i, encara que no és un requisit, permetrà la seva reutilització en altres components.
Usabilitat i informes
Resultats d'aprenentatge:
- Dissenya interfícies gràfiques identificant i aplicant criteris d’usabilitat.
- Crea informes avaluant i utilitzant eines gràfiques.
Referències
- 10 Usability Heuristics for User Interface Design
- The Beginner’s Guide to Information Architecture in UX
- Designing for Web Accessibility
User Experience (UX)
La UX, o User eXperience, és com percep l'usuari final l'ús del teu producte, sistema o servei. Inclou emocions, creences, preferències, percepcions, respostes físiques i psicològiques. I tot això, tant abans, durant com després de l'ús.
Una de les teories que intenten explicar la UX és la dels tres cercles de l'arquitectura de la informació. Segons aquesta, els tres components a tenir en compte per a dissenyar un producte digital són:
- El context: el negoci o la missió.
- Els usuaris: les necessitats.
- El contingut: la solució tècnica.
Els elements de la UX són (segons Garrett), de més abstracte a més concret:
- Els objectius del generals del servei: si és de negoci, creatiu, social, etc.
- Les necessitats de l'usuari, relacionat amb el seu origen i una segmentació.
- Les especificacions funcionals per a complir les necessitats de l'usuari.
- El disseny de la interacció: ha de facilitar les tasques, i defineix com interactua l'usuari amb les funcionalitats.
- El disseny de la informació: com es presenta per a facilitar la seva comprensió.
- El disseny de la interfície que facilita les interaccions.
- El disseny visual (look-and-feel).
Les facetes de la UX que ha de reflectir el nostre producte o servei (segons Morville) són:
- Útil: ha d'omplir una necessitat dels usuaris.
- Usable: ha de ser simple i fàcil d'utilitzar, trobar-ho familiar, amb poc aprenentatge.
- Desitjable: ha de provocar apreciació i emocions favorables amb la imatge, identitat i marca.
- Localitzable: ha de ser fàcil trobar tot allò que es busca.
- Accessible: hem de proporcionar accessibilitat a persones amb discapacitats.
- Confiable: el disseny també influeix la credibilitat i confiança dels usuaris.
- Valuable: si és amb ànim de lucre, ha de contribuir a l'objectiu i millorar la satisfacció de client. Si no ho és, ha de avançar la seva missió.
Les tasques a realitzar associades a l'UX són variades, i generen diferents perfils de dissenyadors:
- Un dissenyador UX ha de fer recerca, identificar necessitats i crear fluxes de tasques.
- El dissenyador d'UI s'encarrega de la part cosmètica: la tipografia, els colors, l'espaiat, les icones, etc.
Usabilitat
- Visibilitat de l'estat
- Reflex del món real
- Control i llibertat
- Consistència i estàndards
- Prevenció d'errors
- Reconèixer millor que recordar
- Flexibilitat i eficiència
- Disseny minimalista i estètic
- Identificació d'errors
- Ajuda i documentació
La usabilitat és la capacitat d'un sistema de proporcionar les condicions perquè els seus usuaris puguin realitzar les seves tasques de forma segura, efectiva i eficient, i que ho facin gaudint l'experiència. COm hem vist, es tracta d'una de les facetes de la UX.
Aquests són 10 principis heurístics per a dissenyar interfícies usables (segons Nielsen).
- Visibilitat de l'estat del sistema. Cal compartir l'estat, i no actuar sense informar, donant feedback tan aviat com sigui possible.
- Reflex del món real. No hi ha d'haver vocabulari desconegut, cal reflectir la terminologia més familiar.
- Control i llibertat de l'usuari. Permetre desfer i refer, saber com cancel.lar i sortir.
- Consistència i estàndards. Interna, al producte i a una família, i externa, amb els estàndards de la indústria.
- Prevenció d'errors. Evitar els errors en situacions proclius, sempre comprovar-los i informar si cal alguna acció.
- Reconèixer millor que recordar. Reduir la quantitat d'informació que cal recordar, ajudar contextualment millor que fer tutorials.
- Flexibilitat i eficiència. Proporcionar dreceres, personalització i customització per a permetre diferents formes de portar a terme accions.
- Disseny minimalista i estètic. Centrar-se en els qüestions essencials, traient aspectes innecessaris i prioritzant els objectius principals.
- Identificació d'errors. Els errors s'han d'expressar en un llenguatge planer que indiqui el problema de forma precisa i ofereixi una solució constructiva.
- Ajuda i documentació. Ajuda fàcil de buscar, en el context i amb passes concretes a realitzar.
Visibilitat de l'estat
Només podem canviar l'estat del sistema si sabem quin és aquest estat. Un cop tenim un objectiu, hi ha dos processos problemàtics o bretxes:
- l'avaluació: quin és l'estat actual del sistema?
- l'execució: com puc utilitzar el sistema?
Alguns consells a seguir:
- Dissenyar el sistema per a informar de l'estat per a poder actuar sobre ell, i veure com les accions modifiquen l'estat (interdependència). A més, el disseny ha de ser familiar per als usuaris. Per exemple: el comptaquilòmetres i l'accelerador.
- Donar una realimentació apropiada un cop es produeix la interacció, si la operació està en progrés i si ha acabat.
- L'estat i les possibles interaccions ha de mostrar-se de forma senzilla, convidant els usuaris a actuar.
Aquests són les opcions comunicatives que tenim:
- Indicadors: fem destacar un element per a informar l'usuari que se l'ha d'atendre. Es pot fer amb icones, variacions tipogràfiques, canvis de mida o animacions. Són contextuals, apareixen condicionalment i són passius (no cal actuar).
- Validacions: són missatges d'error sobre un problema a una entrada per part de l'usuari, que haurà d'actuar. Es pot utilitzar una icona.
- Notificacions: alerten l'usuari sobre esdeveniments generals al sistema no relacionats amb accions immediates. Poden ser contextuals o globals, i poden requerir o no accions.
Reflex del món real
Cal tenir en compte que nosaltres (dissenyadors) no som el mateix que els usuaris. Els termes que utilitzem li han de resultar familiars sense caldre consultar un diccionari.
La solució seria utilitzar objectes i activitats del món real. Si el model mental (teoria de com funciona el sistema) de l'usuari sobre el sistema s'assembla al de la realitat, li facilitem la comprensió i li diem que el coneixem i ens importa.
Control i llibertat
Alguns aspectes a considerar:
- Sempre permetre anar enrere.
- Que anar enrere sigui el que espera l'usuari.
- Que els enllaços de tancar, sortir o cancel.lar siguin visibles i estiguin on cal.
- Que es pugui cancel.lar una acció en qualsevol punt intermedi i, si cal, distingir-ho de tancar.
- Poder desfer una acció fàcilment.
- Que desfer sigui visible, i ho sigui mentre es pugui dur a terme.
Consistència i estàndards
Hi ha unes convencions que caldria respectar. Tenim internes i externes:
- Les internes dins el nostre sistema o sistemes es pot guiar mitjançant un manual d'identitat de marca.
- Les externes poden basar-se en les convencions i estàndards establerts a la indústria. Per exemple, com funciona la navegació, quin aspecte tenen les pàgines d'inici, l'aspecte dels enllaços o dels botons, com s'introdueix la informació als formularis, com funcionen les cistelles de la compra, etc.
Alguns nivells als quals podem actuar:
- El visual. Per exemple, l'hamburguesa del menú per a llocs mòbils.
- La pàgina i la distribució de botons (ordre i colors).
- La introducció de dades de forma guiada segons el seu tipus (telèfons, dates, etc.).
- El contingut, mantenint un to consistent de comunicació.
Prevenció d'errors
Tenim dos tipus d'errors:
- Relliscada: és inconscient, per un descuit, l'usuari volia fer una acció però acaba fent una altra, de forma accidental.
- Errors: és conscient, perquè el model mental de l'usuari no encaixa amb el disseny. L'acció acaba sent inapropiada per a la tasca que volem completar, perquè no hem entès com funciona el sistema.
Per a prevenir relliscades:
- Incloure limitacions a les entrades de dades.
- Oferir suggeriments i bons valors per defecte (tasques repetitives). Es poden utilitzar valors més freqüents o representatius.
- Permetre formats alternatius per a introduir certs tipus de dades.
Per a prevenir errors:
- Fes una bona visualització de l'estat.
- Recull dades dels usuaris per veure on fallen.
- Segueix les convencions de disseny.
- Permet fer una previsualització del resultat.
Per a prevenir els dos tipus d'errors:
- No obligar a recordar moltes coses.
- Confirmar abans d'accions destructives.
- Permetre el desfer.
- Avisar d'errors abans de que passin.
Reconèixer millor que recordar
Cal fer visibles i fàcilment accessibles:
- La informació necessària per a aconseguir un objectiu.
- Les funcions de la interfície, com botons, navegació i altres elements.
Per exemple:
- Tenir un historial o contingut visitat prèviament.
- La revelació progressiva amaga les funcions avançades o por habituals a espais secundaris.
- Ajuda contextual i consells en lloc de tutorials.
Flexibilitat i eficiència
No tots els usuaris tenen les mateixes necessitats. Cal:
- Tenir mètodes diferents per a acomplir la mateixa tasca, segons les preferències.
- Permetre l'usuari customitzar la interfície segons les necessitats, però deixant uns bons valors per defecte.
- També pot ser que la customització la faci el sistema, llavors es diu personalització.
- Tenir acceleradors per a usuaris avançats, que no afecten als primerencs. Per exemple, gestos o macros.
Disseny minimalista i estètic
Els aspectes estètics són importants. Les primeres impressions són importants, com també ho són la percepció visual per sobre de l'experiència. A més, reforcen la identitat de marca.
Per altra banda, el disseny minimalista demana que hi hagi tots els elements per a suportar les tasques de l'usuari, però cap altra més. Tenir elements de més pot amagar els elements necessaris. Podem reduir el soroll del disseny:
- Aprofitant patrons universals de disseny amb connotacions positives. Per exemple, certs tipus d'imatges, com paisatges.
- Acceptar que la bellesa està a l'ull de l'espectador, i que hem de considerar les persones.
Els cinc principis de disseny visual poden ajudar a assolir aquest disseny:
- Escala: utilitzar mides relatives a la importància i prioritat dins de la composició dels elements.
- Jerarquia visual: guiar l'ull dins de la pàgina perquè atengui els elements en ordre d'importància.
- Balanç: passa quan hi ha una càrrega de senyals visuals distribuïda de forma igualitària als dos costats d'un eix imaginari.
- Contrast: la juxtaposició d'elements no iguals per a mostrar elements diferents.
- Principis gestalt: la tendència a percebre diversos elements individuals com a un tot, ja que ens resulta una forma més estable.
Identificació d'errors
Segueix aquesta guia per a escriure els teus missatges d'error:
- Cal que sigui explícit, indicant que alguna cosa concreta ha fallat.
- Ha de ser fàcil de llegir per a qualsevol persona, sense codis ni abreviacions.
- Ha de ser respectuós.
- Ha de ser precís, sense vaguetats genèriques.
- Ha d'aconsellar constructivament com resoldre el problema.
A més, els errors s'han de mostrar amb tractaments visuals que permetin identificar-los i reconèixer-los. Utilitza els visuals de les convencions, com text vermell o negreta.
Ajuda i documentació
Tenim dos tipus d'ajuda: proactiva i reactiva.
L'ajuda proactiva, abans de que es produeixi un error. L'onboarding (primer cop que veiem la interfície) i els consells contextuals són d'aquest tipus. S'orienta a familiaritzar l'usuari amb l'interfície. Pot estar fora del flux de treball i s'ignoren (push) o contextuals i són útils (pull). Guia:
- Mantenir-la curta i en el punt on som per no distreure l'usuari.
- Afavorir la pull sobre la push.
- Les revelacions push haurien de ser fàcils d'ignorar.
- Ha de ser accessible des de qualsevol lloc (fins i tot quan s'han ignorat).
L'ajuda reactiva, amb materials de consulta. S'orienta a respondre preguntes, consultar problemes o ajudar a usuaris que volen ser experts. De vegades s'orienten amb un FAQ. Guia:
- Assegurar-se que la documentació és comprensiva i detallada.
- La informació més important s'ha de presentar abans.
- Considerar l'ús de gràfics i vídeos com a font secundària.
- Permetre la cerca.
- Organitzar per categories.
- Subratllar el contingut visitat freqüentment.
Accessibilitat Web
A continuació es mostren una sèrie de consells en funció de l'activitat a què es refereixen: el disseny, la creació de continguts i el desenvolupament.
Disseny accessible
- Proporcioneu un contrast suficient entre el primer pla i el fons.
- No utilitzeu només el color per transmetre informació.
- Assegureu-vos que els elements interactius siguin fàcils d'identificar: botons i enllaços.
- Proporcioneu opcions de navegació clares i coherents: capçaleres, molles de pa, mapes.
- Assegureu-vos que els elements del formulari inclouen etiquetes associades clarament.
- Proporcioneu feedback fàcilment identificable: icones, colors, etc.
- Utilitzeu els encapçalaments i l'espaiat per agrupar contingut relacionat: faciliteu la comprensió.
- Creeu dissenys per a diferents mides de visualització: responsivitat al mòbil.
- Incloeu alternatives d'imatge i mitjans al vostre disseny: textos, captions, etc.
- Proporcioneu controls per al contingut que s'inicia automàticament: als carrousels, sliders, vídeos, so de fons, etc.
Contingut accessible
- Proporcioneu títols de pàgines informatius i únics.
- Utilitzar els encapçalaments (i subencapçalaments) per transmetre significat i estructura.
- Fes que el text de l'enllaç tingui significat: evita el "clica aquí".
- Escriu alternatives de text significatives per a imatges que tinguin una funció (no per a les decoratives).
- Crea transcripcions i subtítols per a multimèdia.
- Proporcioneu instruccions clares: a les instruccions, guies i als missatges d'error. Sense utilitzar llenguatge tècnic.
- Mantingueu el contingut clar i concís.
Desenvolupament accessible
- Associa una etiqueta a cada control de formulari (label for).
- Incloeu text alternatiu per a les imatges (alt).
- Identificar l'idioma de la pàgina i els canvis d'idioma (html lang).
- Utilitzeu les marques per transmetre significat i estructura: HTML semàntic com section, article, aside, ul, etc.
- Ajudeu els usuaris a evitar i corregir errors: indicar on hi ha el problema, explicar-ho i suggerir correccions.
- Reflecteix l'ordre de lectura en l'ordre del codi: evita que el CSS sigui qui marqui aquest ordre.
- Escriu codi que s'adapti a la tecnologia de l'usuari: responsivitat.
- Donar significat als elements interactius no estàndard: utilitza WAI-ARIA.
- Assegureu-vos que tots els elements interactius siguin accessibles amb el teclat.
- Eviteu CAPTCHA sempre que sigui possible.
Informes
La visualització de dades és el procés de representar les dades gràficament per obtenir informació i facilitar la presa de decisions. D3.js (Data-Driven Documents) és una potent biblioteca de JavaScript que permet als desenvolupadors crear visualitzacions de dades interactives i dinàmiques en aplicacions web.
Preparació i distribució
Resultats d'aprenentatge:
- Avalua el funcionament d’aplicacions dissenyant i executant proves.
- Documenta aplicacions seleccionant i utilitzant eines específiques.
- Prepara aplicacions per a la seva distribució avaluant i analitzant eines específiques.
Referències
- The Practical Test Pyramid
- Testing JavaScript Applications
- Mocks Aren't Stubs
- Jest Getting Started
- Vitest Getting Started
- Testing React Apps
- React Testing Library Cheatsheet
- Testing Overview
Proves
Tenir codi llest per a producció requereix fer proves. Com que no podem tenir milers de testers, sorgeix la necessitat d'automatitzar-les. Un procés que va de la mà de les pràctiques de desenvolupament àgil i el CD (Continuous delivery).
La piràmide de les proves
La piràmide de les proves és una metàfora visual que descriu les capes de les proves:
- Proves unitàries: validació dels blocs més atòmics del software, les funcions.
- Proves d'integració: validació que diferents peces del software funcionen juntes. Per exemple, comunicació amb la base de dades, la xarxa o el sistem d'arxius.
- Proves end to end (E2E): validació des del punt de vista de l'usuari, tractant el software com una caixa negra.
En el món real, aquesta categorització no és estricta, hi ha proves que poden estar al mig d'aquestes capes.
. | Unitàries | Integració | End-to-end |
---|---|---|---|
Objectiu principal | funcions individuals | integració de funcions | funcionalitat d'usuari |
Quantitat | Nombroses | Una mica freqüent | Escassa |
Velocitat | Molt alta | Mitjana | Lenta |
Freqüència d'execució | Alta, quan es desenvolupen una funció | Regular, quan es desenvolupa una funcionalitat | Quan s'acaba una funcionalitat |
Feedback | Entrada i sortida per a la funció | Comportament problemàtic | Funcionalitat incorrecta |
Cost | Baix: petita, fàcil d'actualitzar, executar i entendre | Moderat | Costós |
Coneixement de l'aplicació | Acoblat al codi | Codi, bases de dades, xarxa, arxius | Inconscient |
Beneficis | Feedback ràpid durant el desenvolupament, evitar regressions, documentació | Ús de llibreries de tercers, comprova efectes secundaris | Funcionament correcte de l'usuari |
Tècniques
Qualitat de les proves
Aquestes són algunes pistes per a aconseguir proves que siguin mantenibles:
- Les proves són tan importants com el codi.
- Prova només una funcionalitat per prova. Que sigui curta farà que sigui més clara.
- Escriure el nostre codi amb funcions petites ens ajudarà a fer proves més granulars.
- Estructura les proves amb "arrange, act, assert" o bé "given, when, then".
- La claredat és més important que no repetir-se.
Arrange, act i assert
L'estructura recomanada d'una prova és una seqüència de tres passos:
- Arrange: prepara les entrades i els objectius de la prova.
- Act: invoca el comportament, cridant una funció, interactuant amb un API, una pàgina web, etc.
- Assert: comprova que el resultat és l'esperat.
Test Doubles
Els test doubles són una tècnica que permet introduir una versió simplificada (o falsa) de les dades o funcions reals que permeten reduir la complexitat i facilitar les proves. Aquests objectes falsos poden classificar-se, de menys a més específic, en:
- Dummy: objectes que es passen però no s'utilitzen.
- Fake: implementacions que funcionen, però no serveixen per a producció.
- Stubs: proporcionen respostes enllaunades a certes preguntes, no responent a res que no hi hagi a la prova.
- Spies: són stubs que guarden informació de com van ser cridats.
- Mocks: estan programats amb expectatives, i per tant poden comprovar si la crida no s'espera, o falta alguna crida, llençant excepcions. Per tant, abans d'utilitzar-los cal indicar quines són les expectatives. Molts desenvolupadors utilitzen "mocks" per parlar de forma general dels dobles.
Millors pràctiques:
- Si es pot, evitar els dobles i treballar amb implementacions reals.
- De vegades no és possible, per diferents raons: volem provar certs escenaris (d'èxit o error) però no els podem generar, o bé és molt lent, o no volem treballar amb les dades reals. En general, escenaris on el codi té side effects.
- Llavors ens cal simular comportaments de certa dependència o servei extern.
- Entre els possibles dobles, es convenient seleccionar el menys específic (millor un dummy que un mock).
- Un cop seleccionat el doble, millor implementar-lo que utilitzar un framework.
Test-driven development
El TDD (desenvolupament guiat per proves) és una pràctica de desenvolupament que utilitza les proves unitàries per a escriure codi, i ho fa seguint el següent procediment:
- Afegir un nou test
- Executar tots els tests. La nova prova ha de fallar.
- Escriure el codi més senzill que permeti passar el test.
- Totes les proves han de funcionar novament.
- Fer refactoring quan calgui, utilitzant els tests per assegurar-se que la funcionalitat es preserva.
Eines
Jest i Vitest
El framework de proves JS més conegut és Jest. L'alternativa més interessant és Vitest, completament compatible amb Jest, més ràpid i que funciona amb mòduls.
Les proves JS s'emparellen amb el codi que es vol testar. Per exemple, feature.js
tindria les proves a feature.test.js
. Dins d'aquest arxiu s'han de poder trobar crides del tipus:
test('descripció de la prova', () => {
// codi de la prova (arrange, act, assert)
});
Alternativament, també s'utilitza la funció it
(sinònim) en lloc de test
.
Podem utilitzar les següents funcions per a actuar abans i després de les proves:
beforeAll(() => {
// s'executa abans de totes les proves
});
beforeEach(() => {
// s'executa abans de cada prova
});
afterEach(() => {
// s'executa després de cada prova
});
afterAll(() => {
// s'executa després de totes les proves
});
Arrange
L'arrange ha de preparar les entrades i els objectius de la prova. Això pot incloure la selecció dels nodes del DOM, la creació d'objectes, la configuració de l'estat inicial, la creació de mocks, etc.
Per a fer la selecció dels nodes, podem utilitzar document.querySelector
o document.querySelectorAll
. Però aquesta no és la millor forma, ja que no és el que faria un usuari (veure la Testing Library).
Act
Per al pas act, podríem utilitzar JavaScript directament, però és més recomanable utilitzar una llibreria que simuli l'usuari (veure la Testing Library).
Assert
Per al pas assert, tenim l'estructura expect(receivedValue).matcher(expectedValue)
.
Els matchers més habituals són els següents:
toBe
: per a comprovar valors primitius.toEqual
: per a comprovar objectes o arrays.not.matcher(expectedValue)
: per a negar qualsevol matcher.toBeNull, toBeUndefined, toBeDefined, toBeTruthy, toBeFalsy
: per a comprovar truthiness.toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeLessThanOrEqual
: per a números.toMatch(/.../)
: per a expressions regulars.toContain
: per a comprovar si un array o iterable conté un ítem.toThrow
: per a comprovar si es llença una excepció. Permet comprovar el text també.
Per exemple, expect(2 + 2).toBe(4)
comprova que 2+2 són 4.
La Testing Library afegeix nous matchers a jest.
Testing Library
La Testing Library és un complement a jest o vitest que afegeix noves funcionalitats per a fer proves.
Aquesta llibreria conté:
- Queries, uns mètodes per trobar elements a una pàgina. getBy, queryBy i findBy.
fireEvent
iuserEvent
, per a simular esdeveniments que permeten interactuar amb el DOM.act
iwaitFor
, per a esperar rerenders o actualitzacions d'estat asíncrones.- Matchers addicionals per a jest/vitest
A més, és aplicable tan a aplicacions vanilla JS com a React o d'altres frameworks.
Queries
Les queries poden ser de tres tipus, segons els seu comportament:
- getBy: retorna un element o llença una excepció si no el troba.
- queryBy: retorna un element o null si no el troba.
- findBy: retorna una promesa que es resoldrà quan trobi l'element.
També hi ha les queries per a múltiples elements:
- getAllBy: retorna un array d'elements o llença una excepció si no en troba cap.
- queryAllBy: retorna un array d'elements o un array buit si no en troba cap.
- findAllBy: retorna una promesa que es resoldrà quan trobi els elements.
El format get és el més recomanat, ja que si no troba l'element, llença una excepció i atura la prova. Es recomana utilitzar les queries en aquest ordre:
- Les queries accessibles a tothom, amb
getByRole
,getByLabelText
,getByPlaceholderText
,getByText
,getByDisplayValue
. - Les queries semàntiques, amb
getByAltText
,getByTitle
. - Els testid, amb
getByTestId
. Implica l'ús de l'atributdata-testid
als elements. És el menys aconsellable, però evita l'ús d'altres atributs, com araid
oclass
, associats normalment a l'estil.
Exemples de queries:
const button = getByRole('button', { name: /submit/i });
const input = getByLabelText('Username');
const input = getByPlaceholderText('Username');
const text = getByText('Hello, world!');
const input = getByDisplayValue('Hello, world!');
const img = getByAltText('A close-up of a cat');
const element = getByTitle('A description of the element');
Les queries del getByRole() permeten buscar dins d'un rol. Hi ha un nombre de rols establers per l'estàndard WAI-Aria. Alguns elements tenen un valor implícit, i també es poden definir amb l'atribut role
d'un element HTML.
Les opcions més comunes d'aquesta funció són:
name
: relaciona els elements pel seu nom accessible.level
: s'utilitza per a les funcions d'encapçalament per especificar nivells (h1
,h2
, etc.).expanded
: per a elements ampliables (true
ofalse
).checked
: per a la casella de selecció i els elements de ràdio (true
ofalse
).pressed
: per als elementsbutton
que indiquen l'estat de commutació (true
ofalse
).selected
: per a elements comoption
que indica l'estat de selecció (true
ofalse
).- `: inclou elements que estan visualment ocults però encara accessibles.
Alguns examples de queries:
screen.getByRole("button", { name: "Click Me" });
screen.getByRole("heading", { level: 2 });
screen.getByRole("combobox", { expanded: true });
screen.getByRole("button", { name: "Submit" });
screen.getByRole("option", { selected: true });
El name
de les opcions es pot derivar de (en ordre de preferència):
aria-label
aria-labelledby
- Text intern (per botons, capçaleres, etc.)
- Atribut
alt
(imatges) - Elements
label
associats (per a form inputs, etc.) - Atribut
title
(si no es troba cap altre)
fireEvent i userEvent
fireEvent
és una funció que permet simular esdeveniments. userEvent
és una llibreria que permet simular esdeveniments com ho faria un usuari.
Per exemple, fireEvent.click(button)
simula un clic sobre un botó. userEvent.click(button)
també simula un clic sobre un botó, però ho fa com ho faria un usuari.
Es recomana utilitzar userEvent
sempre que sigui possible, ja que simula millor el comportament d'un usuari.
act i waitFor
act
permet esperar fins que s'han produït tots els rerenders associats a les accions sobre el DOM. Moltes funcions de la testing library ja l'utilitzen, com per exemple fireEvent. Però en altres casos, com per exemple quan s'interactua amb un custom hook, cal utilitzar-lo perquè cal que l'estat s'actualitzi abans de tornar a generar un esdeveniment.
waitFor
és una funció que permet esperar fins que una condició asíncrona es compleixi. Per exemple, waitFor(() => getByText('text'))
. Aquesta funció és útil quan es vol esperar fins que un element aparegui a la pàgina, o fins que desaparegui. També és útil per a esperar fins que un element canvii.
La sintaxi és:
await waitFor(() => {
// codi que comprova la condició
});
Per canviar el temps esperat, es pot passar un objecte amb la propietat timeout
:
await waitFor(() => {
// codi que comprova la condició
}, { timeout: 1000 });
Matchers addicionals
Aquesta llibreria també afegeix matchers addicionals a jest, com ara toBeVisible
, toBeDisabled
, toBeEmpty
, toBeEnabled
, toBeInvalid
, toBeRequired
, toBeValid
, toBeVisible
, toHaveTextContent
, toHaveValue
, toHaveAttribute
, toHaveClass
, toHaveStyle
, toHaveFormValues
, toBeChecked
, toBePartiallyChecked
, toHaveFocus
, toHaveDescription
, toHaveDisplayValue
, toHaveDisplayValue
, toHaveErrorMessage
, toHaveLabel
, toHaveLabelText
, toHaveRole
, toHaveTitle
, toHaveAltText
, toHaveAriaLabel
, toHaveAriaRole
, toHaveAriaSelected
.
JSDOM
Es tracta de la implementació JavaScript d'un navegador sense interfície i molt més ràpid que els convencionals. Permet implementar proves automatitzades amb poca infraestructura i senzillesa.
La seva configuració es mostra en l'apartat corresponent d'aquest document. Implementa l'objecte window.document i les seves API.
Renderització
Components
Per provar components ho podem fer amb render
. Cal cridar-lo abans de fer qualsevol comprovació de l'estat. Per exemple:
render(<Clickdown initialValue={3} />);
const titleEl = screen.getByText(/value is 3/i);
expect(titleEl).toBeInTheDocument();
També podríem tornar a renderitzar el component simulant que ha canviat una prop. O bé desmuntar un component per a comprovar l'efecte:
const { rerender, unmount } = render(<Clickdown initialValue={3} />);
// ...
rerender(<Clickdown initialValue={4} />); // then, check the new value...
unmount(); // then check the effect of unmounting
Hooks
Per provar els custom hooks podem utilitzar renderHook
. Per exemple, si tenim un hook del comptador:
const useCounter = (initialState: number) => {
const [counter, setCounter] = useState<number>(initialState);
return {
value: counter,
increment() {
setCounter(prev => prev + 1);
}
};
};
El podríem provar així:
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.value).toBe(1);
Com es pot veure, s'utilitza act
per assegurar-se que el canvi d'estat associat a increment
s'ha consolidat. També podem utilitzar waitFor
per a esperar un temps un esdeveniment asíncron.
L'objecte retornat per renderHook
també conté rerender
i unmount
, tal com passa amb el render
dels components.
Configuració
Vanilla JS
La llibreria més fàcil d'utilitzar és Vitest, compatible amb la sintaxi de Jest i amb ES modules. Necessitarem npm
per a fer les proves, i això requereix crear un package.json
:
$ npm init -y
$ npm install --save-dev vitest jsdom @testing-library/dom @testing-library/jest-dom
Caldria editar l'entrada script del nostre package.json
:
"test": "vitest --run --reporter verbose"
Si necessitem provar el DOM, caldria afegir l'environment jsdom a la capçalera de qualsevol arxiu de proves. La sintaxi de Vitest és la de Jest:
// @jest-environment jsdom
Podem fer npm test
per a executar els tests.
Per a fer proves DOM amb un script utilitzant Vitest, l'arrange podria ser:
const initialHtml = fs.readFileSync("./index.html", "utf-8");
document.body.innerHTML = initialHtml;
vi.resetModules(); // recarrega mòduls
await import('./script.js'); // aplica un script que estaria a l'HTML
També és interessant importar això si volem afegir els custom matchers de jest a les nostres proves:
import '@testing-library/jest-dom/vitest';
React
Utilitzarem vitest per a les proves React.
Caldria instal.lar vitest, @testing-library/react i @testing-library/jest-dom. A més, si volem afegir matchers addicionals, podem fer-ho important-los des de vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: './src/setup_tests.js',
},
});
Llavors, setup_tests.js podria ser:
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
afterEach(() => {
cleanup(); // clears the jsdom after each test
});
Podem canviar l'entrada test
del package.json
perquè mostri els detalls dels tests:
"test": "vitest --run --reporter verbose",
Podem fer npm test
per a executar els tests.
Referències
Documentació
Aquestes són algunes estratègies per a documentar codi d'aplicacions basades en TypeScript o paquets npm:
- Formats reconeguts pels IDE, com JSDoc i TSDoc.
- Generadors de documentació per a API Rest, com el format OpenAPI i l'eina Swagger.
- Generadors de documentació a partir del codi font, com TypeDoc.
Distribució
Distribució SPA
Un cop tenim la versió de producció amb els arxius estàtics, és senzill fer la distribució.
Les aplicacions SPA poden ser distribuits utilitzant CDNs. En essència, tenim una aplicació que es connectarà al nostre backend i que pot estar a qualsevol servidor de continguts estàtics. Alternativament, podem distribuir-les al mateix servidor que el backend. Això ens estalvia la gestió dels headers CORS i poder gestionar de forma particular com se serveix l'app, però ens cal configurar el backend per a servir simultàniament l'app i el backend (API).
Una altra qüestió és què distribuim del frontend.
- No hi hauria d'haver cap dada confidencial, ja què el navegador permet veure tot el codi font de la nostra aplicació JS.
- Podem esborrar tots els comentaris del codi font amb una transformació
- Hauríem d'evitar mostrar el logging.
- Utilitzar variables al font per a donar valors que canvien en entorn de desenvolupament i de producció. Per exemple, a React, tenim els arxius .env i .env.production que permeten afegir variables. Per exemple, per a tenir una URL diferent de la nostra Rest API en cada entorn.
Distribució automàtica
El procés de distribució automàtica d'aplicacions s'implementa normalment a través d'un pipeline CI/CD que automatitza totes les etapes, des de la gestió del codi fins al desplegament en producció.
A continuació s'explica un possible flux de treball.
Commit de Codi i Control de Versions
El procés comença amb la integració del codi i el seu control de versions, assegurant la traçabilitat de cada modificació.
- Actualització del Codi: Els desenvolupadors fan commit del codi en un sistema de control de versions (com Git).
- Desencadenament del Pipeline: Cada commit (o merge) activa automàticament el pipeline mitjançant webhooks o mecanismes similars.
Integració Contínua (CI)
La fase d'integració contínua compila i prova automàticament el codi per garantir-ne la qualitat.
- Compilació del Codi: El servidor CI (com Jenkins o GitLab CI) recupera el codi i el compila o construeix l'aplicació.
- Proves Automatitzades: S'executen proves unitaris, d'integració i anàlisis de codi per garantir la qualitat.
- Generació d'Artefacte: Si tot és correcte, es crea un artefacte (binari, imatge Docker, JAR, etc.).
Emmagatzematge i Versionat d'Artefactes
Els artefactes resultants es versionen i s'emmagatzemen en repositoris centralitzats per facilitar-ne la gestió.
- Versionat: L'artefacte es versiona segons un esquema (com semàntic, basat en hash o número de build).
- Repositori d'Artefactes: L'artefacte es desa en un repositori centralitzat (per exemple, Nexus, Artifactory o un registre de contenidors com Docker Hub).
Entrega Contínua (CD) a un Entorn de Staging
L'entrega contínua desplega l'artefacte en un entorn de staging on es realitzen proves més exhaustives.
- Desplegament a Staging: El pipeline CD pren l'artefacte i el desplega en un entorn de staging.
- Proves Ampliades: Es realitzen proves d'integració, de seguretat i de rendiment que simulen l'entorn de producció.
- Passos d'Aprovació: En alguns casos, es pot requerir una aprovació manual abans de procedir al desplegament final.
Desplegament Automàtic a Producció
Un cop validat en staging, l'artefacte es distribueix automàticament en producció, amb mecanismes per revertir el desplegament si cal.
- Automatització del Desplegament: Un cop completades les proves, el pipeline desplega automàticament l'artefacte a producció.
- Infraestructura com a Codi: Eines com Terraform, Ansible o Kubernetes s'utilitzen per gestionar la infraestructura i assegurar la consistència.
- Mecanismes de Rollback: Es configuren mecanismes per revertir el desplegament en cas d'errors.
Monitorització i Retroalimentació
Finalment, la monitorització constant i la retroalimentació permeten detectar incidències i optimitzar futurs desplegaments.
- Monitorització en Temps Real: Després del desplegament, es supervisa el rendiment i es recullen logs amb eines com Prometheus, ELK o New Relic.
- Alertes i Resolució d'Errors: Els errors o anomalies generen alertes, permetent una resposta ràpida per part de l'equip de desenvolupament.
- Cicle de Millora Contínua: El feedback obtingut serveix per fer ajustaments i millores en el codi, reiniciant el cicle de desenvolupament.
Diversos
Programació
Principis
Programari
Un cop carregat un programari, la memòria principal d'un ordinador conté instruccions i dades. La CPU executa instruccions màquina, que són instruccions de molt baix nivell que processen dades.
El programari s'expressa habitualment utilitzant un llenguatge de programació, una abstracció que amaga el funcionament de la CPU basat en instruccions màquina.
Un programari es pot descriure pel seu comportament i el seu estat:
- El comportament es defineix amb un flux d'instruccions depenent de les dades.
- Les instruccions poden:
- Fer assignacions, o sigui, modificar l'estat
- Bifurcar-se segons condicions (expressions lògiques) a partir de l'estat
- Llegir dades externes i incorporar-les a l'estat
- Escriure dades utilitzant l'estat
El paradigma més habitual de la programació és l'imperatiu. Aquest, utilitza sentències per a modificar l'estat d'un programa.
Codi
La programació estructurada és una característica dels llenguatges de programació que millora la claredat i qualitat del codi. En tenim:
- Control de flux (if/then/else)
- Repetició (while/for)
- Blocs o àmbits de codi
- Subrutines: procediments i funcions
Dades
Les dades poden ser de diferents tipus:
- Primitius: el tipus més bàsic. Numèrics, caràcters, booleans.
- Estructures: més d'una dada. Per exemple, un array o una llista.
- Referències: apuntadors a altres dades (normalment estructures) utilitzant la seva adreça de memòria.
Operacions per a una dada:
- Reservar espai en memòria (també anomenat instanciar).
- Assignar el seu valor (instrucció d'assignació).
- Obtenir el seu valor.
- Eliminar la dada, alliberant l'espai. Pot ser automàtic (Garbage Collection).
Una variable és un nom simbòlic que s'associa a una dada continguda en memòria.
L'assignació d'una variable sol tenir el format variable = expressió. Això implica avaluar l'expressió i assignar el valor a la variable, reemplaçant el valor anterior.
La dada associada a una variable pot ser mutable o immutable. És mutable si pot ser modificada, i immutable si no. Habitualment, els primitius són immutables.
Funcions
Les funcions són agrupacions de codi reutilitzables que podem cridar utilitzant paràmetres. Aquests paràmetres tenen un àmbit reduït al cos de la funció. Poden tenir un valor de retorn.
Les funcions poden:
- Tenir efectes secundaris: si es modifica l'estat fora del seu àmbit. Per exemple, modificant una variable d'un àmbit més global, o un paràmetre mutable. Això és possible si utilitzem referències.
- Ser pures, si la funció sempre retorna el mateix valor per als mateixos paràmetres i no té efectes secundaris.
Les crides a funcions es gestionen utilitzant una espai de memòria anomenat stack. Cada cop que es fa una crida, creem un nou frame i l'afegim a dalt del stack. A dalt del stack sempre hi ha el frame actiu. Durant la seva execució, aquest frame guarda les dades que s'utilitzen. Quan es fa return, s'elimina aquest frame, i el de sota passa a ser l'actiu.
Objectes
Alguns llenguatges permeten classes, que són plantilles per a crear instàncies d'objectes. Un objecte està compost de:
- Codi que s'executa mitjançant mètodes o operacions (funcions de l'objecte).
- Estat, o dit d'una altra forma, les seves dades associades, habitualment privades.
Un objecte que té operacions que poden modificar el seu estat es diu que és mutable. Si en canvi no es pot, l'objecte és immutable.
Quan un objecte no té comportament, només dades, se l'anomena estructura de dades. Les seves operacions exposen aquestes dades.
Memòria
Tenim dos espais de la memòria principal on es guarden dades: el stack (pila) i el heap (munt).
Un frame del stack s'utilitza dins de l'àmbit d'una funció o mètode per a guardar paràmetres, primitius i referències.
El heap s'utilitza per a guardar estructures, ja que les estructures poden sobreviure l'àmbit on es creen mitjançant les seves referències. En canvi, com que els primitius no es poden referenciar, es creen al stack i que moren amb el return.
Expressions
Una expressió aritmètica té un valor numèric. Es forma utilitzant operadors aritmètics sobre variables, constants o funcions numèriques.
Una expressió lògica té dos possibles valors: True o False. Es formen utilitzant variables i constants lògiques utilitzant operadors lògics (NOT, AND, OR...) i relacionals (=, <, >, <=, >=, <>).
Parèntesis i prioritat
Els operadors de les expressions s'apliquen en funció de la seva prioritat.
Per als aritmètics (de més a menys prioritat):
- ++, --
- *, /, %
- +, -
Per als lògics (de més a menys prioritat):
- NOT
- AND
- OR
- operadors relacionals
Es pot prioritzar una operació lògica o aritmètica utilitzant parèntesis ().
Lleis booleanes
- Commutativa:
- a OR b = b OR a
- a AND b = b AND a
- Identitat:
- a OR False = a
- h AND True = h
- Complement:
- a OR NOT(a) = True
- a AND NOT(a) = False
- Idempotent:
- a OR a = a
- a AND a = a
- Doble negació:
- NOT(NOT(a)) = a
- Associativitat:
- a OR b OR c = (a OR b) OR c = a OR (b OR c)
- a AND b AND c = (a AND b) AND c = a AND (b AND c)
- Distributiva:
- a AND (b OR c) = (a AND b) or (a AND c)
- a OR (b AND c) = (a OR b) AND (a OR c)
- De Morgans:
- NOT(a AND b) = NOT(a) OR NOT(b)
- NOT (a OR b) = NOT(a) AND NOT(b)
- Absorció:
- a AND (a OR b) = a
- a OR (a AND b) = a
Paradigmes
Un paradigma de programació és una forma d'estructurar i representar un programa. Cada llenguatge de programació pot suportar un o més paradigmes.
Aquests són alguns dels paradigmes més presents als llenguatges:
- Imperatiu: el codi té un flux d'execució amb sentències que poden canviar l'estat (és mutable). Aquest concepte s'ha perfeccionat amb altres paradigmes com l'estructurat o el procedural, afegint procediments, variables locals, seqüències, iteració, etc.
- Orientat a objecte: estil procedural que amaga (encapsula) l'estat darrere del concepte d'objecte. Els objectes exposen mètodes que manipulen aquest estat.
- Basat en esdeveniments: estil procedural on el flux d'execució és controlat per l'emissió d'esdeveniments, que poden arribar a qualsevol consumidor. Sol haver una cua al sistema que els despatxa.
- Basat en missatges: similar als esdeveniments, però el que s'envia són missatges, i es fa cap a una adreça concreta. Sol haver una cua al receptor per poder gestionar-los quan estigui preparat.
- Asíncron: en contraposició a la programació síncrona, s'executen tasques, habitualment de forma simultània, sense esperar que es completin per a prosseguir. Associat a conceptes com callbacks, promeses, futurs o async/await.
- Reactiu: programació declarativa basada en esdeveniments asíncrons i fluxos de dades. Es basen en APIs d'operacions on els observadors tenen callbacks per gestionar l'èxit i l'error. Els fluxos poden ser pull i push (back pressure).
- Funcional: les operacions (o funcions) són matemàtiques i no hi ha estat. Les funcions permeten altres funcions com a paràmetres, i totes les dades són immutables. L'objectiu és no tenir efectes secundaris al codi.
- Orientat a dades: estil alhora procedural i funcional. El codi modela únicament les dades, que són immutables. Les operacions estan completament separades de les dades.
- Declaratiu: el codi defineix una lògica, però no diu res respecte del flux d'execució.
- Multi-fil: se'n diu de la possibilitat de dissenyar programes amb més d'un fil d'execució treballant coordinadament.
A continuació es mostren alguns llenguatges de programació els paradigmes principals que els defineixen (tot i permetre altres):
- Java: procedural, orientat a objecte, multi-fil
- Python: procedural, orientat a objecte
- JavaScript: procedural, asíncrona, basat en esdeveniments
- SQL: declaratiu
Conceptes addicionals
Concurrència
La concurrència és l'habilitat d'un sistema de poder executar diverses tasques alhora. En una aplicació es pot implementar de diverses maneres. Per exemple, amb multi-fil. Veure el capítol sobre concurrència.
Mutabilitat
Es diu que una dada és mutable si pot ser modificada després de ser creada. Les dades immutables són més eficients i més útils. En programació concurrent es diu que són inherentment thread-safe, o sigui, no impliquen conflictes quan s'accedeixen per més d'un fil alhora.
Recursivitat
La recursivitat és una forma de resoldre problemes on la solució depèn de la solució a un problema més petit. La forma habitual en la qual la resolen els llenguatges de programació és permetent que una funció es pugui cridar a sí mateixa.
Efectes secundaris
Un efecte secundari es produeix quan una operació fa alguna cosa més que llegir els seus paràmetres i retornar un valor a qui l'ha cridada. Per exemple: llegir una variable no local, canviar un paràmetre mutable, llençar un error o fer E/S. D'una funció sense efectes secundaris se'n diu pura.
Referències
Disseny
- Tècniques per a la solució de problemes
- Com anomenar
- Bones pràctiques per escriure codi
- És dolent el teu codi?
- Principis generals de programació
- Principis de POO (SOLID)
- Dependències a POO
- Arquitectura i fronteres
- REST APIs
- Referències
Tècniques per a la solució de problemes
-
Definir el problema: el punt de partida i l’objectiu
- si el problema te l’ha donat un altre, explica’l amb les teves paraules
- representa el problema amb dibuixos i diagrames
- identifica les coses que no saps
-
Idear un pla
- Descomposició: trencar un problema en parts més senzilles (estructura d’arbre)
- Generalització: abstracció, identificar patrons i reduir el nombre de conceptes
- Patrons senzills: noms: objectes; verbs: operacions; adjectius: propietats; números: variables
- Patrons de control: bucles, subrutines, regles
- Altres tècniques:
- Pensament crític: posa en dubte les teves decisions... i si falla?
- Resoldre un problema concret
- Troba un problema relacionat
- Cercar cap enrere des de l’objectiu… com puc arribar?
- Dissenyar un model (simplificació, representació, dades, interacció)
-
Executar el pla
-
Revisar i estendre (iteració)
Com anomenar
Cada llenguatge de programació té les seves regles tipogràfiques i gramaticals per anomenar els seus elements. Per exemple, les regles tipogràfiques de Java són:
- Els paquets s'escriuen amb lletres minúscules, amb components separats per punts de més genèric a més específic:
org.junit.jupiter.api
. - Les classes tenen una o més paraules, sense abreviatures, amb cadascuna en majúscules:
List
,FutureTask
. - Els mètodes i els camps segueixen la mateixa regla, però la primera lletra ha de ser minúscula:
remove
,ensureCapacity
. - Les variables locals segueixen el mateix criteri, però permeten abreviatures:
i
,houseNum
. - Els paràmetres de tipus són una lletra, on T és un tipus qualsevol, E un tipus d'element d'una col·lecció, K/V una clau i un valor, X una excepció, i R un tipus de retorn.
Els criteris gramaticals solen ser més flexibles:
- Les classes són noms o frases nominals:
Thread
,PriorityQueue
. - Les interfícies poden anomenar-se com les classes o bé com un adjectiu que acaba amb
able
oible
:Runnable
,Iterable
. - Els mètodes que realitzen alguna acció són verbs o frases verbals:
append
,drawImage
. - Els mètodes que retornen un boolean solen començar per
is
ohas
, i després poden seguir amb un nom, frase nominal o qualsevol paraula o frase que funcioni com adjectiu:isDigit
,isProbablePrime
,isEmpty
,isEnabled
,hasSiblings
. - Els mètodes que retornen un valor no boolean o un atribut de l'objecte s'anomenen amb un nom o frase nominal, o una frase que comença per get:
size
,hashCode
,getTime
. - Els mètodes getters i setters són una construcció obsoleta en molts casos, utilitzar-los amb cautela.
- Els camps de tipus boolean solen anomenar-se com l'accessor, però ometent 'is' o 'has':
initialized
,started
. - Els camps d'altres tipus no boolean solen ser noms o frases nominals:
height
,digits
,bodyStyle
.
Bones pràctiques per escriure codi
Estàs escrivint codi per llegir-lo en el futur, o bé per un altre...
- Les classes han de ser petites, per sota de 500 línies, i han de tenir un nombre limitat de mètodes
- Els mètodes han de ser petits, per sota de 30 línies, i han de fer una feina concreta
- Has d’escriure el codi perquè s’expliqui a ell mateix, però on no arribis, utilitza comentaris
- No facis línies massa llargues, com a molt de 120 caràcters
- Manté baix el nivell de sagnat del codi, i intenta no superar els 3-4 nivells
- Si hi ha dades d’entrada, crea-les des del codi per no haver d’introduir-les des del teclat
- Anomena les classes, els mètodes i les variables amb els criteris ja explicats
- Decideix el teu estil i segueix-lo de forma consistent
És dolent el teu codi?
- És massa rígid? Es poden canviar els detalls interns d'aquest mòdul en el futur sense tocar el codi d'altres mòduls i altres capes? El codi rígid és el que té dependències que serpentegen en tantes direccions que no es pot fer un canvi aïllat sense canviar-ho tot al voltant.
- És massa fràgil? Seria difícil trobar llocs on fer canvis i refactoritzar en el futur? El codi fràgil es trenca de formes estranyes i que no es poden predir.
- Hauria de ser una característica reutilitzable? Si ho fos, el codi depèn de mòduls no desitjats que es podrien evitar? Vols una banana, però el que obtens és un goril·la agafant-la i tota la jungla amb ell.
Si mirem de prop, el fil conductor dels tres problemes esmentats és l'acoblament. Els mòduls depenen els uns dels altres de maneres no desitjades i resulten en un codi espagueti.
El codi hauria d'estar desacoblat entre els mòduls i les capes. Les polítiques d'alt nivell i les abstraccions no haurien de dependre de detalls de baix nivell, sinó d'abstraccions: caldria invertir la dependència dels mòduls als llocs necessaris. I escriure classes que només fan una cosa i només tenen un motiu per canviar.
El codi bo hauria d'explicar què està fent. Hauria de ser avorrit de llegir. Tot hauria de ser perfectament obvi. Això és bon codi - Robert Martin.
Principis generals de programació
- DRY (Don't repeat yourself): no et repeteixis.
- Principi de l'abstracció: cada peça significant s'ha d'implementar en només un lloc del codi font.
- KISS (Keep it simple): mantenir la senzillesa.
- Evita crear YAGNI (no ho necessitaràs).
- Fes la feina més senzilla que sigui funcional.
- No em facis pensar.
- Escriu el codi per qui l'haurà de mantenir.
- El principi de la mínima sorpresa.
- Minimitzar l'acoblament i maximitzar la cohesió.
- Amagar els detalls de la implementació.
- La llei de Demeter: el codi només s'ha de comunicar amb les seves relacions directes.
- Evitar la optimització prematura: només si funciona i es lent.
- Reutilitzar codi és bo: el fa més llegible.
- Separació d'interessos: àrees de diferents funcionalitats han de tenir pocs solapaments.
- Els usuaris d'una classe han de dependre de la seva interfície pública, però la classe no ha de dependre dels usuaris.
Principis de POO (SOLID)
- Responsabilitat única (SRP): un component de codi ha de fer una sola feina i ben definida. Només caldrà modificar-lo si necessitem canviar aquesta feina. Això millora la cohesió i redueix possibles errors. Code smell: quan vull canviar una funcionalitat, una altra no relacionada queda afectada, i cal refer-la.
- Obert/tancat: les entitats software han d'estar obertes a ser esteses i tancades a ser modificades. Si cal estendre la funcionalitat és millor afegir codi que canviar l'existent. Això es pot fer amb abstracció, derivant classes i utilitzant polimorfisme, i amb encapsulació. Code smell: quan vull afegir funcionalitat, es produeix una cascada de canvis.
- Substitució Liskov: qualsevol classe que hereta d'una altra (pare) pot ser utilitzada d'igual forma que la pare sense conèixer les diferències entre elles. Per tant, quan heretem no hem de canviar el comportament que defineix la classe pare.
- Segregació d'interfície: es millor tenir moltes interfícies de client específiques que una sola de propòsit general. Així, evitem que els clients depenguin de mètodes que no utilitzen.
- Inversió de dependència: les dependències han de ser sobre abstraccions, no sobre concrecions. Es resumeix en dues qüestions:
- Els mòduls d'alt nivell (més abstractes) no han d'importar res de mòduls de baix nivell (més concrets). Els dos han de dependre d'abstraccions (p. ex. interfícies).
- Les abstraccions no han de dependre dels detalls. Són els detalls (implementacions concretes) les que han de dependre de les abstraccions.
Dependències a POO
A POO, si tenim dues classes A i B, i A necessita a B per fer la seva feina, llavors A té una dependència de B. Per a resoldre aquesta dependència podem fer:
- Que A crei o obtingui un un objecte B. La classe A té el control de la dependència.
- Que A rebi un objecte B. Algú altre li proporciona, sense que A s'hagi de preocupar.
A més, quan necessitem un objecte poden passar almenys dues coses:
- Que necessitem una nova instància cada cop. Per exemple, les factories creen múltiples objectes.
- Que necessitem una única instància compartida. Aquesta situació es relaciona amb el patró singleton.
La inversió de control (IoC) és un principi de POO que cedeix a un contenidor o framework la tasca de controlar la creació d'instàncies d'objectes.
Tenim principalment dues formes d'implementar IoC:
- L'injecció de dependència (DI), on un contenidor pren el control i fa crides al nostre codi per proporcionar les dependències d'un objecte. Hi ha principalment dos tipus: de construcció i de setter.
- El Service Locator, que introdueix un nou objecte al nostre codebase, el Locator, que permet resoldre dependències d'una certa classe.
Aquestes tècniques es poden implementar per a subministrar instàncies úniques o múltiples. Per a permetre instàncies úniques, com serveis, utilitzen un mapa o diccionari d'instàncies accessibles pel nom de la classe.
L'objectiu d'aquestes tècniques és seguir el principi d'inversió de dependència: fem les dependències sobre abstraccions (interfícies). Això permet separar l'ús de la construcció, i podem substituir les implementacions sense afectar el codi.
Arquitectura i fronteres
Definició de frontera
L'arquitectura d'un sistema defineix com es divideix en components, quines són les fronteres o límits (boundaries) d'aquests components i com es comuniquen a través d'aquestes. Quan no hi ha fronteres parlem del monòlit.
El propòsit d'aquestes fronteres és facilitar el seu desenvolupament, gestió, manteniment i evolució. Una bona definició de les fronteres redueix l'acoblament, facilitant la flexibilitat de modificar unes parts sense afectar unes altres, i evita la degradació gradual de l'arquitectura. D'això se'n diu arquitectura evolutiva.
- Facilita el desenvolupament, perquè permet desenvolupar les parts de forma independent, a ritmes diferents.
- Facilita les proves, podem crear objectes que simulen comportament (test doubles), com dummies, stubs, mocks o fakes.
- Facilita els canvis i l'evolució, ja que podem canviar la implementació d'un comportament sense afectar la resta.
Les dependències entre components haurien de ser un graf acíclic dirigit. Això evita les dependències circulars, que augmenta l'acoblament dels components i limita la possibilitat de reutilitzar-los de forma individual.
Tipologia de fronteres
Tenim dos processos per a crear fronteres: separació horitzontal i vertical.
- L'horitzontal crea les fronteres entre àrees tècniques del sistema. Per exemple, una API, la lògica de negoci i la comunicació amb la base de dades. Els canvis impliquen habitualment diferents capes del sistema. Pot ser un problema si les capes les gestionen diferents equips de desenvolupament.
- La vertical crea la frontera entre àrees funcionals del sistema. S'utilitza amb microserveis. Per exemple, la gestió d'usuaris o la creació de comandes. Els canvis en aquest tipus de separació són més àgils.
Les fronteres poden combinar aspectes horitzontals i verticals i utilitzar diferents mecanismes de separació:
- Codi font: utilitzant classes i interfícies per poder comunicar-se mitjançant mètodes sense veure la implementació. Si es fa bé (actuant amb bona fe), permet aïllar les parts per permetre múltiples equips treballant. És l'únic mecanisme dels monòlits.
- Components vinculats dinàmicament: es fa una separació amb components desplegables, per exemple, arxius JAR. Es comuniquen amb crides a mètodes, i poden utilitzar el principi d'inversió de dependència per a establir les relacions entre ells.
- Processos locals: tenim processos locals que estan a la mateixa màquina. Poden comunicar-se utilitzant memòria compartida o sòcols. Permeten utilitzar diferents entorns de desenvolupament i tecnologies, sempre que es comparteixi el protocol.
- Serveis: permet que els serveis estiguin a diferents màquines i utilitzin la xarxa. No se sol compartir la base de dades (mala pràctica). S'estableixen protocols estàndards basats en la xarxa, com REST. Exemple dels microserveis.
Els dos primers mecanismes es basen en la definició d'interfícies i els altres dos en la definició de protocols.
Disseny per contracte
La correcta definició de la frontera és essencial. El disseny per contracte és una forma de dissenyar formalment les interfícies dels components d'un software respecte de qui els crida, o clients. Aquest contracte té dues parts:
- Els requisits que demana el component als clients.
- Les promeses fetes pel component als clients.
Resumint, si el client compleix els requisits, el component promet complir el contracte que defineix.
Si canviem un contracte, volem que els clients no quedin afectats. Per assegurar-nos utilitzem la frase "no requerir més ni prometre menys": si el canvi no requereix més dels clients ni promet menys, la nova especificació es compatible i no trencarà el funcionament del client.
Algunes pràctiques per a seguir el disseny per contracte:
-
Documentar el contracte amb comentaris, responent quins són els requisits i les promeses que es fan. En resum, explicant exactament què es fa sense haver de saber com. A Java es fa al javadoc de la classe i dels mètodes públics.
-
Validar arguments dels mètodes i constructors públics. És raonable no fer-ho amb els privats, ja que només es criden per la mateixa classe. D'això també se'n diu precondicions. A Java, aquesta validació sol produir excepcions unchecked com IllegalArgumentException, NullPointerException o IllegalStateException que no cal obligatòriament documentar.
-
Validar les promeses que fa un mètode al seu client. D'això també se'n diu postcondicions. Per exemple, comprovar valors, tipus de retorn, errors i excepcions que es produeixen. Es pot fer just abans d'acabar un mètode públic.
-
Validar l'estat de l'objecte. D'això també se'n diu invariants de classe. Implica mantenir una sèrie de condicions sobre l'estat entre les crides a mètodes públics. Si l'objecte és immutable, només cal validar l'estat al constructor. Si és mutable, a cada mètode que canvia l'estat.
-
Opcionalment, aspectes sobre el rendiment (temps i espai).
Estils d'arquitectura
L'arquitectura d'una solució pot ser monolítica o distribuïda. La distribuïda sol basar-se en client-servidor, tot i que també tenim la solució peer-to-peer.
A continuació revisarem alguns dels estils d'arquitectura.
- Multicapa (multitier). Basada en la client-servidor, estableix una sèrie de capes independents que permeten desenvolupar cada capa de forma prou independentment. Les capes habituals són:
- Presentació
- Aplicació
- Dades
- Microkernel. Es tracta d'una arquitectura amb una funcionalitat central que permet afegir funcionalitats addicionals en forma de plug-ins.
- Orientada a esdeveniments (event-driven). Basada en la comunicació asíncrona d'esdeveniments. Associada al concepte de "event-broker", un middleware que s'encarrega d'encaminar esdeveniments entre sistemes que implementen el patró productor-consumidor. Els missatges poden ser guardats a cues fins a ser processats.
- Microserveis. Es traca d'un ecosistema de serveis d'un propòsit senzill que es comuniquen mitjançant un gateway d'APIs. Cada servei utilitza les seves pròpies dades.
Patrons d'arquitectura
L'arquitectura hexagonal és un model arquitectural que permet separat el core de negoci (o domini) de la infraestructura (UI, base de dades, APIs, frameworks, etc.). Ho fa proposant els adaptadors i els ports.
Els adaptadors fan la interacció de la nostra aplicació cap al món. Tenim dos tipus:
- Els primaris: puts d'entrada de l'aplicació, operats principalment pels usuaris (UI, API Rest, CLIs).
- Els secundaris: actors secundaris com les bases de dades o serveis de tercers.
Si volem seguir el principi d'inversió de dependència, les capes internes no poden dependre de les externes. O sigui, les dependències han de ser de fora a dins: dels adaptadors cap al core.
Els ports són fronteres abstractes a l'exterior, per exemple, utilitzant interfícies, i els adaptadors són implementacions concretes dels ports, habitualment injectades:
- Els adaptadors primaris depenen de ports d'entrada, i dirigeixen l'aplicació (API, GUI, CLI).
- Els adaptadors secundaris implementen ports de sortida, i són dirigits per l'aplicació (BBDD, API clients).
DDD (Domain-drive design) és una estratègia de disseny de software focalitzat en el modelatge del software, i que té l'objectiu de replicar una àrea temàtica o domini (domain) gràcies als coneixements dels experts d'aquesta àrea.
Dins del DDD hi trobem diversos tipus de models, com les entitats, que tenen identitat, o les value objects, objectes immutables sense identitat. També hi ha aggregates, que són altres models dirigits per una entitat arrel, i que són unitats de consistència, concurrència i distribució.
De vegades hem de comunicar el nostre domini amb altres externs, que provoquen una dependència externa. És una mala estratègia permetre que es filtri aquest model extern cap al nostre, i la forma d'evitar-ho és construir una capa anti-corrupció. La idea tècnica es pot implementar utilitzant els patrons Facade i Adapter.
L'arquitectura clean afegeix el concepte d'entitats i casos d'ús al core. Les dues es basen en el DDD. Les capes que defineix, de més interna a més externa són:
- Les entitats: objectes de negoci amb comportament
- Els casos d'ús (use cases): mapejat de la funcionalitat de les user stories
- Els adaptadors de interfícies (controladors, vistes, presentadors)
- Els frameworks i eines (BBDD, serveis de tercers)
El principi de la regla de dependència diu que cap capa interna ha de dependre d'una externa. Això es manifesta de la següent forma:
- Sense dependències:
- Les entitats no depenen de ningú.
- Dependències cap a dins:
- Els casos d'ús depenen de les entitats, la capa més interna.
- Els adaptadors d'entrada depenen dels casos d'ús.
- Dependències cap a fora, però abstractes (inversió de dependència):
- Els casos d'ús depenen dels adaptadors de sortida. Per exemple, per fer persistència.
El diagrama mostra una arquitectura hexagonal amb conceptes DDD (clean). Les fletxes negres indiquen dependència d'una interfície i les blanques, implementació.
Cal no confondre la direcció de les dependències amb el flux de control. Per exemple, un flux de control d'una aplicació MVP podria ser controlador => cas d'ús => entitats => cas d'ús => presentador
.
Finalment, cal dir que el cablejat dels components de l'arquitectura es fa des de fora de l'aplicació, creant les dependències, connectant-les i iniciant l'aplicació. El patró es diu composition root. Aquí sol utilitzar-se la injecció de constructor i, si hi ha contenidors que fan inversió de control, és l'únic lloc on haurien d'aparèixer.
Diagrames d'arquitectura
El model C4 és un model que permet visualitzar l'arquitectura d'una solució. Descriu quatre nivells de diagrames, de més generals a més concrets:
- De context de sistema: mostra el sistema software i el seu context al voltant.
- De contenidor: mostra els contenidors dins d'un sistema software i com es relacionen.
- De component: mostra els components d'un contenidor i les seves interaccions.
- De codi: mostra la implementació del codi amb diagrames UML, diagrames ER o similars.
Hi ha quatre abstraccions que poden aparèixer en el nostre diagrama:
- La persona: actors o rols d'un sistema software.
- El sistema software: el sistema que estàs modelant, i que es relaciona amb altres sistemes externs.
- Un contenidor: una aplicació o un magatzem de dades. Per exemple:
- Un backend web
- Un frontend web
- Una aplicació desktop (client)
- Una app mòbil
- Una aplicació de consola o script
- Una funció serverless (cloud)
- Una base de dades
- Un magatzem al núvol
- El sistema d'arxius
- Un component: grup de funcionalitats encapsulada darrere d'una interfície (o contracte). Tots els components d'un contenidor solen executar-se en el mateix espai de processos o màquina virtual.
Alguns consells per a dibuixar diagrames:
- No hi ha una forma estàndard de fer-ho.
- Han de reflectir la realitat. Abstraccions primer, notació després.
- Conté blocs per als diferents components (SoC). Alguna cosa modular amb una interfície/fronteres. Els components contenen codi.
- Agrupar els components que treballen junts amb caixes (contenidors): DB schema, app mòbil, backend server-side app, console app, windows service.
- Ha d'incloure les dades, la lògica de negoci i la interfície d'usuari.
- Fletxes sempre direccionals per indicar la direcció del flux (petició) amb descripció.
- No cal afegir respostes (fletxes de tornada) dient OK, només qui les origina.
- Ser consistent en forma i color, evitar acrònims no coneguts.
REST APIs
Criteris de disseny
- Identificar els recursos que són part de l'API i els seus IDs.
- Definir l'URI del recurs, o també anomenades endpoints. Utilitzen noms (no verbs).
- Quan es retorna un sol recurs es retorna informació completa. Quan es retornen col·leccions se sol reduir la informació a l'estrictament necessària.
- Assignar els mètodes adequats.
Mètodes
El content type a utilitzar amb les peticions és "application/json". Usos dels mètodes HTTP:
- GET recupera una representació del recurs a l'URI especificat.
- Si es troba, 200 (Ok). El cos del missatge de resposta conté els detalls del recurs sol·licitat.
- Si no es troba, 404 (Not Found).
- També es pot retornar 204 (No Content) si ha anat bé, però no es retorna cap contingut.
- Si les dades enviades no són vàlides, 400 (Bad Request). El cos pot incloure informació addicional sobre el problema.
- POST crea un recurs nou a l'URI especificat. El cos del missatge de sol·licitud proporciona els detalls del nou recurs. Tingueu en compte que POST també es pot utilitzar per activar operacions que en realitat no creen recursos.
- Si es crea un nou recurs, 201 (Created). El recurs pot retornar-se al cos.
- Si es fa algun procés, però no es crea res, 200 (Ok). El cos pot incloure el resultat de l'operació. Alternativament, si no hi ha resultat, es pot retornar 204 (No Content) sense cos.
- Si les dades enviades no són vàlides, 400 (Bad Request). El cos pot incloure informació addicional sobre el problema.
- PUT crea o substitueix el recurs a l'URI especificat. El cos del missatge de sol·licitud especifica el recurs que s'ha de crear o actualitzar.
- Si es crea un nou recurs, 201 (Created).
- Si s'actualitza, 200 (Ok) o 204 (No Content).
- Si no es possible l'actualització, 409 (Conflict).
- PATCH realitza una actualització parcial d'un recurs. El cos de la sol·licitud especifica el conjunt de canvis que cal aplicar al recurs. Les respostes podrien ser com les de PUT.
- DELETE elimina el recurs a l'URI especificat.
- Si funciona, 204 (No Content), sense retornar cap informació.
- Si es retorna alguna informació també es pot utilitzar 200 (Ok).
- Si no existeix, 404 (Not Found).
Aquests són alguns exemples d'endpoints i com se solen utilitzar segons els mètodes:
Recurs | POST | GET | PUT | DELETE |
---|---|---|---|---|
/customers | Crear un nou client | Obtenir tots els clients | Actualitzar tots els clients | Esborrar tots els clients |
/customers/1 | N/A | Obtenir els detalls del client 1 | Actualitzar els detalls del client 1, si existeix | Esborrar client 1 |
/customers/1/orders | Crear una nova comanda per al client 1 | Obtenir totes les comandes del client 1 | Actualitzar totes les comandes del client 1 | Esborrar totes les comandes del client 1 |
Bones pràctiques
- Utilitzar JSON com a format per a enviar i rebre dades (cos).
- Utilitzar noms en lloc de verbs per als endpoints.
- Els endpoints de col·leccions s'han d'anomenar amb noms plurals.
- No utilitzar verbs a la URI. P. ex. si cal fer una acció, fer-ho sobre un nom amb un paràmetre 'action' en la query.
- Envia codis d'estat per a gestionar els errors.
- Utilitza filtres, ordenació i paginació a les dades.
- Genera una bona documentació de l'API (OpenAPI).
Filtres, ordenació i paginació
Quan s'exposa un conjunt de recursos a un endpoint, cal evitar retornar una quantitat molt gran de dades. L'API hauria de permetre especificar filtres a l'URI: recursos?filtre1=valor1&filtre2=valor2…
. També caldria especificar a l'URI com obtenir només una part dels resultats, quan poden ser molts.
L'ordenació també es pot realitzar amb un paràmetre del tipus recursos?order_by=criteri
.
Cal utilitzar paginació sempre que una col·lecció de recursos pugui ser gran perquè pugui créixer sense límit. Es pot fer principalment de tres formes:
- offset:
recursos?limit=nombre&offset=nombre
permet utilitzar els paràmetres SQL per limitar els resultats. L'opció més senzilla, però poc òptima per a offsets alts: cal obtenir tots els registres anteriors en la query. - keyset: filtra pel valor d'un camp que defineix l'ordre. Per exemple, la data de creació. Podem utilitzar
recursos?limit=nombre&from_date=data
. - seek: similar a l'anterior, però utilitzant una primary key. Podem utilitzar
recursos?limit=nombre&after_id=id
.
Per a la paginació, pots utilitzar un valor de límit per defecte si no es diu res. Per exemple, 20. No s'hauria de permetre un valor qualsevol per a aquest paràmetre (ha d'estat limitat). També pots retornar informació al cos de la resposta que pugui ajudar al client a gestionar el resultat.
Referències
- The C4 model for visualising software architecture
- PlantUML
- UML diagrams with plantUML
- Inversion of Control Containers and the Dependency Injection pattern
- Best Practices for Designing and Implementing a Library in Java
- Dependency injection
- Software Architecture Boundaries
- Acyclic Dependencies Principle
- Top 20 Design Heuristics
- Design Principles (Bob Martin)
- Listing of Arthur Riel's heuristics
- Design by Contract
- Hexagonal architecture
- The Clean Architecture
- Hexagonal Architecture with Java and Spring
- Domain-centric Architectures (Clean and Hexagonal) for Dummies
- RESTful web API design
- REST API Design: Filtering, Sorting, and Pagination
- What is REST
- OpenAPI
- Architectural boundaries
- Simpler Encapsulation with Immutability
- Effective Java by Joshua Bloch
Java
POO
Conceptes de POO
Abstracció
L'abstracció amaga la complexitat a l'usuari i només mostra la informació rellevant. Permet centrar-nos en el "què" en lloc del "com". Els detalls d'un mètode abstracte són implementats de forma separa per cada classe.
Objectius: reusabilitat de codi, flexibilitat d'implementació, herència múltiple.
Java: classes abstractes (abstracció parcial) i interfícies (abstracció total).
Sintaxi: abstract
(classes abstractes) i interface
, implements
(interfícies).
Encapsulació
L'encapsulació lliga les dades i els mètodes relacionats dins d'una classe. També protegeix les dades (estat) fent els camps privats i donant accés a ells només mitjançant els mètodes relacionats que implementen les regles de negoci i invariants.
Objectius: protecció de dades (estat), llegibilitat del codi.
Java: camps privats i mètodes públics (només els exposats).
Sintaxi: private, setX(), getX()
.
Herència
Amb l'objectiu de reusar codi, l'herència permet que una classe fill hereti les característiques (camps i mètodes) d'una altra classe pare. La relació es diu is-a. L'herència s'utilitza quan sabem que:
- Les dues classes es troben en el mateix domini lògic.
- La superclasse és un contracte que no canviarà (interfície o classe abstracta).
- Les millores realitzades per la subclasse són principalment additives.
L'alternativa és la composició.
Objectius: reusabilitat de codi, llegibilitat del codi.
Java: Classe pare i class fill. Alternativa vàlida: patrons de composició i delegació.
Sintaxi: extends
.
Composició
La composició és una alternativa de l'herència per a reusar codi. La relació es diu has-a. Es pot aconseguir utilitzant instàncies d'altres objectes. És una tècnica preferible a la herència, ja que redueix l'acoblament del codi. Un tipus específic de composició és l'agregació, que no implica la propietat dels objectes interns i, per tant, tampoc la destrucció d'aquests quan l'objecte contenidor es destrueix.
Objectius: reusabilitat de codi.
Java: variables d'instància d'altres classes.
Sintaxi: private ClassName instanceName
.
Polimorfisme
El polimorfisme permet substituir i estendre la funcionalitat d'objectes que tenen una mateixa interfície per uns altres durant el temps d'execució (dynamic binding).
Objectius: llegibilitat del codi, flexibilitat del codi.
Java: sobreescriptura de mètode (dinàmic), menys rellevant: sobrecàrrega de mètode (estàtic).
Sintaxi: myMethod() myMethod(int x) myMethod(int x, String y)
(estàtic) i ParentClass.myMethod() ChildClass.myMethod()
(dinàmic).
Relacions entre objectes
Aquests són els tipus de relacions comunes entre objectes:
- Associació: un objecte fa referència a un altre.
- Dependència: un objecte rep una referència en una de les seves operacions.
- Agregació: un objecte fa referència a diversos objectes.
- Composició: un objecte crea diversos objectes.
Aquest és un diagrama que les mostra.
- Herència de Number i implementació de Comparable
- Associació entre Child1 i Parent1
- Associació navegable des de Child2 cap a Parent2
- Agregació: Wheel pot existir sense Car
- Composició: Heart no pot existir sense Human
- Dependència: Object1 usa l'Object2
Tipus d'objectes
Un objecte és una entitat que pot integrar comportament i estat. La pràctica més acceptada és la d'encapsular l'estat darrere del comportament, perquè formen part de la implementació, que és convenient amagar.
Una primera classificació dels objectes es pot fer en funció de dues característiques: si tenen estat (stateful) o no (stateless), i en el cas que en tinguin, si són immutables o no (mutables).
- Un objecte té estat si el seu comportament depèn d'interaccions prèvies. Els objectes sense estat tenen un comportament esperable que només depèn dels paràmetres de les crides, com passa a la programació funcional.
- Un objecte immutable té un estat que no pot canviar-se després de ser creat. Permeten escriure codi més fàcil d'entendre i segurs quan hi ha concurrència.
Segons el seu ús, podem fer una classificació dels dos tipus d'objectes que podem trobar més freqüentment a Java.
- Abstract Data Types (ADT): són objectes amb comportament i estat encapsulat, i habitualment mutables. Cal definir detalladament el seu comportament, i evitar retornar referències a objectes mutables del seu estat.
- Estructures de dades: són objectes amb només estat, i poden ser immutables. Permeten transferir dades o representar un estat.
Els ADT
Patrons de disseny
Un patró de disseny és una solució general a un problema comú i recurrent en el disseny de programari. Un patró de disseny no és un disseny acabat que es pot transformar directament en codi; és una descripció o plantilla per resoldre un problema que es pot utilitzar en moltes situacions diferents.
Tenim algunes categories generals:
- De comportament: identifiquen patrons de comunicació entre objectes.
- Estructurals: faciliten el disseny quan s'han d'establir relacions entre entitats.
- Creacionals: relacionats amb mecanismes de creació d'objectes de la forma més adient per cada cas.
- Concurrència: tracten el paradigma de programació multifil.
A continuació es mostren alguns patrons importants.
Patrons de comportament
Command
El patró command encapsula una sol·licitud com a objecte, de manera que us permetrà parametrizar altres objectes amb diferents peticions, cues o peticions de registre i donar suport a operacions reversibles.
Tenim dos actors principals: el Client crea el ConcreteCommand i assigna el seu Receiver. L'Invoker té Commands que pot executar.
Iterator
El patró iterator proporciona una manera d'accedir als elements d'un objecte agregat seqüencialment sense exposar la seva representació subjacent.
Exemples: java.util.Iterator i java.util.Enumeration
Observer
El patró observer defineix una dependència entre molts objectes de manera que quan un objecte canvia d'estat, tots els seus dependents són notificats i actualitzats automàticament.
Exemples: java.util.EventListener
State
El patró state permet a un objecte alterar el seu comportament quan canvia el seu estat intern. L'objecte semblarà canviar de classe.
El Context pot tenir un nombre de States. Quan cridem request(), el que fem es cridar el handle corresponent del State actual. Un Client no coneix el funcionament intern dels States.
Strategy
El patró strategy s’utilitza quan tenim diversos algorismes per a una tasca específica i el client decideix que s’utilitzi la implementació real en temps d’execució.
És molt similar a State, però és el Client qui habitualment escull la Strategy. Exemples: Collections.sort() amb el paràmetre Comparator
Dependency Injection
El patró de Dependency Injection permet a un objecte rebre les instàncies d'altres objectes de que depèn. Això permet que el client no s'hagi de referir a instàncies concretes, seguint el principi d'inversió de dependència. S'utilitza per implementar el principi de la inversió de control (IoC) mitjançant un contenidor.
Hi ha tres tipus d'injecció: Constructor, Setter i Interface.
- Constructor: li diem al contenidor quina implementació s'utilitza per als paràmetres del constructor, que poden ser interfícies.
- Setter: li diem al contenidor quines propietats (amb setters) cal instanciar d'un cert objecte.
- Interface: li diem al contenidor que el nostre client rebrà les dependències mitjançant un mètode (del tipus setService).
Per utilitzar-los, cal configurar el contenidor. Es pot fer programàticament o mitjançant un arxiu de configuració.
L'objecte que injecta les dependències es diu injector, i els objectes de que depèn solen ser serveis.
Service Locator
El Service Locator és una alternativa al Dependency Injector, amb la particularitat que el codi client té una dependència del contenidor, que utilitza per localitzar tots els serveis que necessita. És a dir, no es produeix la injecció automàtica.
No es tracta d'una Factory, ja que no crea els serveis cada cop, només si cal. Es tracta més bé d'un registre.
Template method
El patró template method defineix l'esquelet d'un algorisme d'un mètode, diferint alguns passos a subclasses. Aquest patró permet que les subclasses redefineixin certs passos d'un algorisme sense canviar l’estructura de l’algoritme.
El templateMethod fa ús del subMethod. Exemple: construcció d'una casa de fusta o de vidre
Patrons estructurals
Adapter
El patró adapter converteix la interfície d'una classe en una altra interfície que els clients esperen. L'adaptador permet que les classes treballin conjuntament, tot i tenir interfícies incompatibles.
El ConcreteAdapter està composat amb l'Adaptee, que li permet una operació addicional: adaptedOperation. Exemple: un capità que només pot utilitzar barques de rem i no pot navegar
Composite
El patró composite permet compondre objectes en estructures d’arbre per representar jerarquies parcials. Composite permet als clients tractar objectes individuals i composicions d'objectes de manera uniforme.
Tant els objectes individuals com els composats poden ser tractats igual amb operation.
Decorator
El patró decorator atribueix dinàmicament responsabilitats addicionals a un objecte. Els decorators proporcionen una alternativa flexible a la subclasse per ampliar la funcionalitat.
Podem afegir un comportament al ConcreteDecorator. Exemple: trol decorat amb una porra
Facade
El patró facade proporciona una interfície unificada i més senzilla a un conjunt d’interfícies d’un subsistema. La façana defineix una interfície de més alt nivell que facilita la utilització del subsistema, seguint el principi del "Least knowledge".
Exemple: treballadors de la mina d'or
Proxy
El patró de proxy proporciona un substitut per a un altre objecte per controlar-ne l'accés.
Qualsevol Client pot tractar el Proxy com el RealSubject, ja que implementen Subject. Exemple: els tres mags que entren a la torre
Patrons creacionals
Separa la construcció d'un objecte complex de la seva representació, de forma que el mateix procés de construcció pot generar diferents representacions.
Builder
Factory method
Factory method defineix una interfície per crear un objecte, però permet que les subclasses decideixin quina classe s'inicia. Permet diferir la instància de classe a subclasses.
El mètode factoryMethod és abstracte, i s'encarrega de crear Products. Permet seguir el principi de "Dependency inversion": evitar dependències de tipus concrets. Exemple: el regne que necessita tres objectes temàtics
Singleton
El patró de Singleton assegura que una classe només té una instància i li proporciona un punt d'accés global.
Exemple: només pot haver una torre d'ivori
Referències
- OOPs concepts in Java, Infographic
- Programming paradigm
- Composition vs. Inheritance: How to Choose?
- Java Design Patterns
- Difference between Association, Aggregation and Composition in UML, Java and Object Oriented Programming
- PlantUML Design Patterns
- Composition vs Inheritance
Píndoles Java
- Noms a Java
- Valors inicials de les variables
- Modificadors d'accés
- Expressions i conversió de tipus
- Emmagatzematge de variables
- Precedència d'operadors
- Local vs Instance vs Class variables
- Checked versus unchecked exceptions
- Interpretació de les excepcions
- Genèrics
- Optional
- Classes dins de classes
- Expressions Lambda
- Referències a mètodes
- Mètodes default i static a interfícies
- Programació funcional
- Modules
- Línia de comanda
- Referències
Noms a Java
CamelCase: pràctica d’escriure frases o paraules compostes eliminant espais i posant en majúscula la primera lletra de cada paraula.
Pot ser UpperCamelCase o lowerCamelCase.
Es recomanable evitar caràcters que no siguin lletres o números.
- Classes (i Interfaces): noms en UpperCamelCase
- Mètodes: verbs en lowerCamelCase
- Variables: en lowerCamelCase. Han de ser mnemònics i evitar variables d’una lletra, excepte per temporals: i,j,k,m,n (sencers) i c,d,e (caràcters)
- Constants: paraules en majúscules separades per subratllat (underscore)
- Paquets (packages): paraules en minúscules separades per punts. Solen tenir format de domini invertit (com.domain.subdomain). S’ha d’evitar el default package (sense nom)
Valors inicials de les variables
- Cada variable de classe, d'instància o component d'un array s'inicialitza amb un valor per defecte quan es crea:
- Per a byte, el valor per defecte és zero, o sigui, el valor de (byte)0.
- Per a short, el valor per defecte és zero, o sigui, el valor de (short)0.
- Per a int, el valor per defecte és zero, o sigui, 0.
- Per a long, el valor per defecte és zero, o sigui, 0L.
- Per a float, el valor per defecte és positive zero, o sigui, 0.0f.
- Per a double, el valor per defecte és positive zero, o sigui, 0.0d.
- Per a char, el valor per defecte és el null character, o sigui, '\u0000'.
- Per a boolean, el valor per defecte és false.
- Per a tots els tipus referència, el valor per defecte és null.
- Cada paràmetre d'un mètode o constructor s'inicialitza amb el valor de l'argument corresponent quan s'invoca el mètode o constructor.
- El paràmetre d'una excepció s'inicialitza amb l'objecte que representa l'excepció.
- Una variable local ha de ser inicialitzada explícitament abans de ser utilitzada, amb una inicialització o assignació.
Modificadors d'accés
Els modificadors d'accés indiquen el nivell d'accés per a variables, mètodes i constructors. En tenim quatre, de menys a més restrictius:
- public: sense restriccions, totes les classes de tots els paquets poden accedir.
- protected: només podem accedir des del mateix paquet o des de qualsevol subclasse, encara que no estigui al mateix paquet.
- default (sense paraula clau): només podem accedir des del mateix paquet.
- private: només es pot accedir des de la mateixa classe.
Expressions i conversió de tipus
Ordre de les expressions
A Java, les expressions s'avaluen d'esquerra a dreta.
Java no executarà parts de l'expressió quan no sigui necessari. En particular:
- Si tenim expressions AND i el resultat d'una a l'esquerra és
false
, ja no es continua avaluant a la dreta. - Si tenim expressions OR i el resultat d'una a l'esquerra és
true
, ja no es continua avaluant a la dreta.
Tipus primitius
Quan diversos tipus intervenen en una expressió, tots es converteixen al mateix tipus mitjançant unes normes de promoció. Per començar, tots els char / byte / short es promouen a int. Si hi ha algun long, a long. Si hi ha algun float, a float. I si hi ha algun double, a double.
La conversió només es pot fer amb tipus numèrics (exclou el boolean). Hi ha de dos tipus:
- Widening: cap a un tipus més ample, no cal utilitzar cap notació.
- Narrowing: cap a un tipus més estret, cal utilitzar el cast: ( tipus ). La conversió pot perdre informació.
Objectes
Tenim dos tipus:
- Upcasting: quan volem passar el tipus d'un objecte des de la subclasse a una superclasse.
- Downcasting: quan volem passar el tipus d'un objecte des d'una superclasse a una subclasse.
L'operació de downcasting sol venir precedida de l'ús de l'operador instanceof
. Aquest operador retorna true si la classe és del tipus que es pregunta, si és una subclasse o si implementa la interfície.
Autoboxing i unboxing
El boxing / unboxing permet convertir automàticament entre els tipus primitius i les classes embolcall (boolean/Boolean, byte/Byte, char/Character, float/Float, int/Integer, long/Long, short/Short i double/Double
):
- Boxing: conversió automàtica que es produeix des d'un tipus primitiu cap a un objecte.
- Unboxing: conversió automàtica que es produeix des d'un objecte cap a un tipus primitiu.
Character ch = 'a'; // autoboxing
int val = new Integer(-8); // unboxing
Emmagatzematge de variables
Java passa els paràmetres d'un mètode per valor. Però el valor d'un objecte és una referència. Això també inclou qualsevol array (de primitius o objectes).
Per tant, mai podem modificar el valor d'una variable primitiva, ni el d'un objecte (la referència). El que es pot fer és, si l'objecte és mutable, modificar-lo.
Precedència d'operadors
level | Operator | Description | Associativity |
---|---|---|---|
16 | [] . () | access array element access object member parentheses | left to right |
15 | ++ -- | unary post-increment unary post-decrement | not associative |
14 | ++ -- + - ! ~ | unary pre-increment unary pre-decrement unary plus unary minus unary logical NOT unary bitwise NOT | right to left |
13 | () new | cast object creation | right to left |
12 | * / % | multiplicative | left to right |
11 | + - + | additive string concatenation | left to right |
10 | << >> >>> | shift | left to right |
9 | < <= > >= instanceof | relational | not associative |
8 | == != | equality | left to right |
7 | & | bitwise AND | left to right |
6 | ^ | bitwise XOR | left to right |
5 | | | bitwise OR | left to right |
4 | && | logical AND | left to right |
3 | || | logical OR | left to right |
2 | ?: | ternary | right to left |
1 | = += -= *= /= %= &= ^= |= <<= >>= >>>= | assignment | right to left |
Local vs Instance vs Class variables
characteristic | Local variable | Instance variable | Class variable |
---|---|---|---|
Where declared | In a method, constructor, or block. | In a class, but outside a method. Typically private. | In a class, but outside a method. Must be declared static. Typically also final. |
Use | Local variables hold values used in computations in a method. | Instance variables hold values that must be referenced by more than one method (for example, components that hold values like text fields, variables that control drawing, etc), or that are essential parts of an object's state that must exist from one method invocation to another. | Class variables are mostly used for constants, variables that never change from their initial value. |
Lifetime | Created when method or constructor is entered. Destroyed on exit. | Created when instance of class is created with new. Destroyed when there are no more references to enclosing object (made available for garbage collection). | Created when the program starts. Destroyed when the program stops. |
Scope/Visibility | Local variables (including formal parameters) are visible only in the method, constructor, or block in which they are declared. Access modifiers (private, public, ...) can not be used with local variables. All local variables are effectively private to the block in which they are declared. No part of the program outside of the method / block can see them. A special case is that local variables declared in the initializer part of a for statement have a scope of the for statement. | Instance (field) variables can been seen by all methods in the class. Which other classes can see them is determined by their declared access: private should be your default choice in declaring them. No other class can see private instance variables. This is regarded as the best choice. Define getter and setter methods if the value has to be gotten or set from outside so that data consistency can be enforced, and to preserve internal representation flexibility. Default (also called package visibility) allows a variable to be seen by any class in the same package. private is preferable. public. Can be seen from any class. Generally a bad idea. protected variables are only visible from any descendant classes. Uncommon, and probably a bad choice. | Same as instance variable, but are often declared public to make constants available to users of the class. |
Declaration | Declare before use anywhere in a method or block. | Declare anywhere at class level (before or after use). | Declare anywhere at class level with static. |
Initial value | None. Must be assigned a value before the first use. | Zero for numbers, false for booleans, or null for object references. May be assigned value at declaration or in constructor. | Same as instance variable, and it addition can be assigned value in special static initializer block. |
Access from outside | Impossible. Local variable names are known only within the method. | Instance variables should be declared private to promote information hiding, so should not be accessed from outside a class. However, in the few cases where there are accessed from outside the class, they must be qualified by an object (eg, myPoint.x). | Class variables are qualified with the class name (eg, Color.BLUE). They can also be qualified with an object, but this is a deceptive style. |
Name syntax | Standard rules. | Standard rules, but are often prefixed to clarify difference from local variables, eg with my, m, or m_ (for member) as in myLength, or this as in this.length. | static public final variables (constants) are all uppercase, otherwise normal naming conventions. Alternatively prefix the variable with "c_" (for class) or something similar. |
Checked versus unchecked exceptions
Unchecked exceptions:
- represent defects in the program (bugs) - often invalid arguments passed to a non-private method. To quote from The Java Programming Language, by Gosling, Arnold, and Holmes: "Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program's logic and cannot be reasonably recovered from at run time."
- are subclasses of
RuntimeException
, and are usually implemented usingIllegalArgumentException
,NullPointerException
, orIllegalStateException
- a method is not obliged to establish a policy for the unchecked exceptions thrown by its implementation (and they almost always do not do so)
Checked exceptions:
- represent invalid conditions in areas outside the immediate control of the program (invalid user input, database problems, network outages, absent files)
- are subclasses of
Exception
- a method is obliged to establish a policy for all checked exceptions thrown by its implementation (either pass the checked exception further up the stack, or handle it somehow)
Interpretació de les excepcions
Hem vist que podem tenir excepcions Checked (subclasses de Exception
) i Unchecked (subclasses de RuntimeException
).
Si mirem la jerarquia de classes, totes extenen Throwable
, la classe pare de totes:
Els tres mètodes més importants de Throwable
són:
public String getMessage()
: conté el missatge d'excepció.public StackTraceElement[] getStackTrace()
: conté la traça de la pila de l'excepció.public Throwable getCause()
: opcionalment, conté una referència a l'excepció que ha causat aquesta. Pot ser una cadena de diverses excepcions.
Stacktrace
Un stacktrace no ha de ser necessàriament un error. Es pot obtenir el valor actual mitjançant aquest codi:
public class TestStackTrace {
public static void one() {
two();
}
public static void two() {
three();
}
public static void three() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (StackTraceElement elem : trace) {
System.out.println(elem);
}
}
public static void main(String[] args) {
one();
}
}
Cause
Si pots, guarda la causa quan facis throw d'una excepció més específica.
try {
doSomething();
} catch (NumberFormatException e) {
throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}
Estructura típica
paquet.NomDeLaException: missatge que explica la excepció al getMessage()
at paquet.Classe.metode(Classe.java:XXX)
at paquet.Classe.metode(Classe.java:XXX)
...
Caused by: paquet.NomDeLaException: missatge que explica la excepció al getMessage()
at paquet.Classe.metode(Classe.java:XXX)
at paquet.Classe.metode(Classe.java:XXX)
...
Exemple
A continuació veiem una excepció amb les causes encadenades (Caused by). Els punts suspensius expressen la repetició de les línies respecte de l'excepció pare.
org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:275)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:152)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:179)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:119)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
at cotxes.CotxesDAO.getEMF(CotxesDAO.java:205)
at cotxes.CotxesDAO.access$400(CotxesDAO.java:15)
at cotxes.CotxesDAO$JPATransaction.result(CotxesDAO.java:223)
at cotxes.CotxesDAO.findMarques(CotxesDAO.java:81)
at cotxes.ProvaCotxes.processCommand(ProvaCotxes.java:69)
at cotxes.ProvaCotxes.prova(ProvaCotxes.java:49)
at cotxes.ProvaCotxes.main(ProvaCotxes.java:18)
Caused by: org.hibernate.exception.JDBCConnectionException: Error calling DriverManager#getConnection
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:115)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.connections.internal.BasicConnectionCreator.convertSqlException(BasicConnectionCreator.java:118)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionCreator.makeConnection(DriverManagerConnectionCreator.java:37)
at org.hibernate.engine.jdbc.connections.internal.BasicConnectionCreator.createConnection(BasicConnectionCreator.java:58)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.addConnections(DriverManagerConnectionProviderImpl.java:363)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.<init>(DriverManagerConnectionProviderImpl.java:282)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.<init>(DriverManagerConnectionProviderImpl.java:260)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections$Builder.build(DriverManagerConnectionProviderImpl.java:401)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.buildPool(DriverManagerConnectionProviderImpl.java:112)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.configure(DriverManagerConnectionProviderImpl.java:75)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:100)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:246)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.buildJdbcConnectionAccess(JdbcEnvironmentInitiator.java:145)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:66)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:94)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
... 20 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:342)
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2197)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2230)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2025)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:778)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:386)
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:208)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionCreator.makeConnection(DriverManagerConnectionCreator.java:34)
... 35 more
Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:211)
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:301)
... 50 more
Genèrics
Mètodes genèrics
- Totes les declaracions genèriques del mètode tenen una secció de paràmetre tipus delimitada per claudàtors d'angle que precedeix el tipus de retorn del mètode (<E> en el següent exemple).
- Cada secció de paràmetres de tipus conté un o més paràmetres de tipus separats per comes. Un paràmetre tipus, també conegut com a variable de tipus, és un identificador que especifica un nom de tipus genèric.
- Els paràmetres de tipus han de ser una lletra majúscula. Convencions: 'T' per tipus, 'E' per a elements de col·leccions, 'S' per a serveis i K i V per a claus i valors de mapes.
- Els paràmetres de tipus es poden utilitzar per declarar el tipus de devolució i actuar com a marcadors per als tipus d’arguments passats al mètode genèric, que es coneixen com a arguments de tipus reals.
- El cos d'un mètode genèric es declara com el de qualsevol altre mètode. Tingueu en compte que els paràmetres de tipus només poden representar tipus de referència, no tipus primitius (com int, double i char).
public static <E> void printArray( E[] inputArray ) {
// Display array elements
for(E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
Paràmetres de tipus delimitat
Hi pot haver moments en què voldreu restringir els tipus de tipus que es permeten passar a un tipus de paràmetre. Per exemple, un mètode que opera sobre números només pot voler acceptar instàncies de Number o de les seves subclasses. Per a què serveixen els paràmetres del tipus de límit.
Per declarar un paràmetre de tipus delimitat, enumereu el nom del paràmetre del tipus, seguit de la paraula clau extends
, seguit tipus delimitant.
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume x is initially the largest
if(y.compareTo(max) > 0) {
max = y; // y is the largest so far
}
if(z.compareTo(max) > 0) {
max = z; // z is the largest now
}
return max; // returns the largest object
}
Classes genèriques
Una declaració de classe genèrica sembla una declaració de classe no genèrica, tret que el nom de classe sigui seguit per una secció de paràmetre tipus.
Com en els mètodes genèrics, la secció de paràmetres de tipus d'una classe genèrica pot tenir un o més paràmetres de tipus separats per comes. Aquestes classes es coneixen com a classes parametrizades o tipus parametrizats perquè accepten un o més paràmetres.
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Interfícies genèriques
Imaginem que tenim aquesta interfície:
interface Container<T> {
T get();
}
La podríem implementar amb una classe genèrica:
public class GenericContainer<T> implements Container<T> {
private T t;
public GenericContainer(T t) {
this.t = t;
}
@Override
public T get() {
return t;
}
}
Aquesta implementació serveix per qualsevol tipus de paràmetre. Però també podem fer-ho concretant el tipus i esborrant el paràmetre:
public class StringContainer implements Container<String> {
private String s;
public StringContainer(String s) {
this.s = s;
}
@Override
public String get() {
return s;
}
}
Aquesta implementació serveix només per a Strings, però les dues implementen Container.
Optional
La classe Optional<T>
és un contenidor que pot o no tenir un valor null
. Si el valor està present, isPresent()
retorna true i get()
retorna el valor.
Ens permet evitar haver de comprovar object != null
al codi contínuament, reduint el nombre de NullPointerException i construint APIs més expressives. Per exemple: no cal explicar si un mètode retorna o no null, si el que retorna és un Optional<T>
.
S'ha d'utilitzar sempre com a valor de retorn d'un mètode. Evitar sempre que sigui un camp de l'objecte o un paràmetre d'un constructor o mètode.
public Optional<String> findName() { // API expressiva
String name = findNameInDatabase();
return Optional.ofNullable(name);
}
Optional<String> optional = findName();
if (optional.isPresent()) {
System.out.println("trobat " + optional.get());
}
else {
System.out.println("no trobat!);
}
Classes dins de classes
Java permet definir classes dins de classes. Això permet agrupar i encapsular, fent més llegible i gestionable el codi.
Classes estàtiques imbricades
És una classe relacionada amb la classe exterior, però que no pot fer referència a les variables d'instància d'aquesta. De fet, és com una classe normal que s'ha imbricat dins d'una altra, per qüestions de conveniència.
class OuterClass {
...
static class StaticNestedClass {
...
}
}
Classes imbricades
Les classes imbricades (no estàtiques) estan associades amb una instància de la classe que les conté.
class OuterClass {
...
class InnerClass {
...
}
}
Si una declaració utilitza una variable o paràmetre amb el mateix nom que una altra a un àmbit que el conté, llavors la declaració encobreix aquesta, i podem accedir-hi amb la sintaxi OuterClass.this.variable
.
Classes locals
Una classe es pot definir localment, en qualsevol bloc de codi. Aquestes classes només poden accedir variables locals que siguin finals o efectivament finals: el seu valor no canvia després d'una declaració amb inicialització.
public class LocalClassExample {
...
public static void metode() {
...
class LocalClass {
...
}
}
}
Classes anònimes
Les classes anònimes permeten declarar i inicialitzar una classe al mateix temps. Són com classes locals, però sense nom. S'utilitzen quan només cal utilitzar una classe un cop. Es consideren expressions.
Es componen de:
- L'operador new.
- El nom d'una interfície a implementar o una classe a estendre.
- Els parèntesis amb els arguments d'un constructor.
- Un bloc de codi de la classe.
...
Runnable tasca = new Runnable() {
@Override
public void run() {
...
}
};
...
Les classes anònimes no poden contenir declaracions de constructors, ni poden accedir a variables locals que no siguin finals o efectivament finals.
Expressions Lambda
Una expressió lambda és una forma d'instanciar una interfície funcional. Una interfície funcional és aquella que només té un mètode abstracte.
Per exemple, partim d'una interfície funcional i un mètode que la utilitza:
@FunctionalInterface
interface MyFunctionalInterface {
String myOnlyMethod(String input);
}
static String process(String input, MyFunctionalInterface mfi) {
return mfi.myOnlyMethod(input);
}
Una expressió lambda permet definir i instanciar una classe que implementa la interfície funcional només escrivint el codi que falta: el seu únic mètode.
Com podem cridar aquest mètode? Sense i amb expressió lambda:
String output = process("input", new MyFunctionalInterface(){
@Override
public String myOnlyMethod(String input) {
return input.toUpperCase();
}
});
// versió lambda
String output = process("input", (input) -> input.toUpperCase());
Les expressions lambda són molt habituals al disseny d'interfícies gràfiques. Exemple de gestió d'un esdeveniment d'un botó (control de tipus Button
):
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Botó clicat!");
}
});
Aquest codi utilitza classes anònimes.
També podem utilitzar expressions Lambda, ja que els gestors d'esdeveniments són interfícies funcionals (un sol mètode abstracte):
button.setOnAction(
event -> System.out.println("Botó clicat!")
);
Veure https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Referències a mètodes
De vegades, una expressió lambda només crida a un mètode. Llavors, és més fàcil i clar fer una referència al mètode. S'utilitza l'operador doble dos punts (double colon) amb el format Objecte::metode
.
Tenim quatre tipus de referències. Veiem exemples a partir d'aquest codi:
class Persona {
private String name;
private int age;
Persona(String name, int age) {
this.name = name;
this.age = age;
}
String getName() {
return name;
}
Integer getAge() {
return age;
}
public int compareByName(Persona p) {
return getName().compareTo(p.getName());
}
public int compareByAge(Persona p) {
return getAge().compareTo(p.getAge());
}
@Override
public String toString() {
return name + ":" + age;
}
}
@FunctionalInterface
interface CreadorPersones {
Persona create(String name, int age);
}
class ProveidorComparadors {
public int compareByName(Persona a, Persona b) {
return a.getName().compareTo(b.getName());
}
public int compareByAge(Persona a, Persona b) {
return a.getAge().compareTo(b.getAge());
}
}
Aquí es veuen els quatre tipus de referències:
// 4: Referència a constructor
CreadorPersones creator; // mètode amb els mateixos paràmetres que el constructor
creator = (name, age) -> new Persona(name, age); // lambda
creator = Persona::new; // ref.mètode
// 1: Referència a mètode estàtic
List<Persona> list = Arrays.asList(createArray(creator));
list.forEach((person) -> System.out.println(person)); // lambda
list.forEach(System.out::println); // ref.mètode
// 2: Referència a mètode d'instància d'un objecte particular
ProveidorComparadors provComparadors = new ProveidorComparadors();
Arrays.sort(createArray(creator), (a, b) -> provComparadors.compareByName(a, b)); // lambda
Arrays.sort(createArray(creator), provComparadors::compareByName); // ref.mètode
// 3: Referència a mètode d'instància d'objecte arbitrari d'un tipus particular
Arrays.sort(createArray(creator), (a, b) -> a.compareByName(b)); // lambda
Arrays.sort(createArray(creator), Persona::compareByName); // ref.mètode
Mètodes default i static a interfícies
Abans de Java 8, les interfícies només podien tenir mètodes abstractes. Java 8 introdueix el concepte de mètodes default que permeten a les interfícies tenir mètodes amb implementació sense afectar les classes que implementen la interfície.
Per definir-los, només cal utilitzar el keyword "default" davant de la signatura.
També s'afegeixen els mètodes static, que permeten agrupar mètodes d'ajuda d'una classe.
public interface Thermometer {
double getCelsius();
default double getFahrenheit() {
return convertToFahrenheit(getCelsius());
}
static double convertToFahrenheit(double celsius) {
return celsius * 1.8 + 32;
}
}
Programació funcional
Una interfície funcional en Java és una que conté un sol mètode abstracte per a implementar. Pot contenir mètodes default i static implementats.
public interface MyFunctionalInterface {
public void execute();
}
També podria contenir mètodes default i static:
public interface MyFunctionalInterface2{
public void execute();
public default void print(String text) {
System.out.println(text);
}
public static void print(String text, PrintWriter writer) throws IOException {
writer.write(text);
}
}
Una interfície funcional es pot implementar amb una expressió lambda:
MyFunctionalInterface lambda = () -> {
System.out.println("Executing...");
}
Java té una sèrie d'interfícies funcionals a la seva llibreria.
Function
public interface Function<T,R> {
public <R> apply(T parameter);
}
Consumer
public interface Consumer<T> {
void accept(T t);
}
Predicate
public interface Predicate {
boolean test(T t);
}
Supplier
public interface Supplier<T> {
T get();
}
Modules
Un mòdul de Java (9+) és un conjunt de paquets reutilitzables. Es defineix utilitzant un arxiu anomenat modules-info.java a l'arrel del codi. El format base és:
module modulename {
// directives
}
El nom del mòdul pot utilitzar punts, com els paquets. Les directives permeten definir:
- Les seves dependències:
requires modulename
. A més, si s'afegeix la paraula clautransitive
al mig, indica que tercers mòduls que el requereixin també tindran aquestes dependències implícitament. - Quins paquets estaran disponibles a altres mòduls:
exports packagename
. - Quins serveis ofereix:
provides interfacename with classname
. - Quins serveix consumeix:
uses interfacename
. - Quins paquets estaran disponibles a altres mòduls mitjançant reflection:
opens packagename [to modulename, modulename...]
.
Regla important a respectar: un paquet només ha d'aparèixer en un mòdul.
A continuació es mostren els module-info.java de tres mòduls que permeten definir una API (my.interface), implementar-la (my.provider) i utilitzar-la (my.consumer).
module my.provider {
requires my.interface;
provides app.api.MyService with app.impl.MyServiceImpl; // ofereix el servei
}
module my.consumer {
requires my.interface;
uses app.api.MyService; // consumeix el servei
}
module my.interface {
exports app.api; // per al consumer i el provider
}
El consumer pot buscar totes les implementacions i instanciar-les. En aquest cas, només trobaria una, la del provider. Però podria haver més mòduls amb més implementacions del mateix servei.
ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service: loader) {
// ...
}
Un servei només s'instancia un cop per cada ServiceLoader.
Línia de comanda
Imaginem que tenim una classe test.HelloModular
amb un main que volem executar, i aquesta depèn d'un jar, stleary-json.jar
, que no és modular.
Si volem comprovar si un jar és modular, tenim dues eines:
$ jar -tf nom-de-larxiu.jar # llista el contingut
$ jar --file=nom-de-larxiu.jar --describe-module # descriu la modularitat
Si no volem utilitzar mòduls en la nostra aplicació, podem compilar i executar així:
$ javac -cp lib/stleary-json.jar -d classes `find src -name *.java`
$ java -cp classes:lib/stleary-json.jar test.HelloModular
Si en canvi volem utilitzar mòduls, necessitem crear un module-info.java
com aquest:
module hello_modular {
requires org.json; // nom que hi ha a Automatic-Module-Name del jar
}
stlearly-json.jar és un mòdul automàtic. El seu nom sol derivar del nom del jar, excepte si hi ha un META-INF/MANIFEST.MF al jar i conté un nom alternatiu amb la clau Automatic-Module-Name. En aquest cas existeix, i el contingut és "org.json".
i compilar i executar així:
$ javac -p lib/stleary-json.jar -d classes `find src -name *.java`
$ java -p classes:lib/stleary-json.jar -m hello_modular/test.HelloModular
També podem crear un jar enlloc d'utilitzar la carpeta classes:
$ jar --create --file lib/hello-modular.jar -C classes .
$ java -p lib/hello-modular.jar:lib/stleary-json.jar -m hello_modular/test.HelloModular
Referències
- Java® Platform, SE & JDK Version 11 API specification
- Java. The Complete Reference (Ninth Edition). Herbert Schildt
- Capítol 20: Input/Output: Exploring java.io.
- Oracle Java SE Support Roadmap
- Infografia de conceptes POO
- Composition vs. Inheritance: How to Choose?
- Difference between Association, Aggregation and Composition in UML, Java and Object Oriented Programming
- Practical Test Pyramid (Martin Fowler)
- Default methods
- Java SE 8 Date and Time
- Main Tools to Create and Build Applications
- Java Modules Cheat Sheet
Glossari Java
- Array
- Classe
- Classe abstracta
- Classe embolcall (wrapper class)
- Constructor
- Fil (thread)
- Interfície fluïda (fluent interface)
- Mètode
- Mètode d'instància
- Mètode de classe
- Multifil
- Objecte
- Objecte immutable
- Objecte mutable
- Procés
- Referència
- Secció crítica
- Sobrecàrrega (mètodes: Overloading)
- Sobreescriptura (mètodes: Overriding)
- Subclasse
- Superclasse
- Synchronized
- Tipus genèric
- Tipus primitiu
- Tipus cru (raw)
- Variable
- Variable d'instància
- Variable de classe
- Variable local
Array
Una col·lecció de dades del mateix tipus amb una posició designada per un sencer. Un array és un objecte del tipus Object.
Classe
Una plantilla que defineix la implementació d’un tipus d’objectes. Té membres: dades (variables d’instància) i funcionalitat (mètodes d’instància).
Classe abstracta
És un tipus de classe on certs detalls de la seva implementació es posposen per ser completats més tard. Poden ser classes que utilitzen el mot "abstract" o bé interfícies (mot "interface").
Classe embolcall (wrapper class)
Són les classes que Java defineix per embolicar els tipus primitius. Són immutables.
Constructor
Un pseudo-mètode d’instància que crea un objecte. Tenen el nom de la classe, i s’invoquen amb “new”.
Fil (thread)
La unitat bàsica d'execució del programa. Un procés pot tenir diversos fils que funcionen simultàniament, cadascun realitzant un treball diferent, com ara esperar esdeveniments o realitzar un treball que requereix temps que el programa no necessita completar abans de continuar.
Interfície fluïda (fluent interface)
És una forma de dissenyar API orientades a objectes basada en l'encadenament de mètodes. L'objectiu és fer que la llegibilitat del codi font sigui més propera a la d'un text escrit.
Mètode
Una funció definida a una classe. Hi ha mètodes d’instància i de classe.
Mètode d'instància
Un mètode que s’invoca respecte a la instància d’una classe.
Mètode de classe
Un mètode que s’invoca sense referir-se a cap objecte particular. També es diu “mètode estàtic”.
Multifil
Descriu un programa que s'ha designat per tenir parts del codi que s'executen concurrentment.
Objecte
És una instància d’una classe, una concreció singular. També es diu “instància” segons el context.
Objecte immutable
Un objecte que NO pot ser modificat després de ser creat.
Objecte mutable
Un objecte que pot ser modificat després de ser creat. Habitualment tenen setters/getters.
Procés
Un espai d'adreces virtual que conté un o més fils.
Referència
Un tipus de variable on el seu valor és l'adreça d’un objecte.
Secció crítica
Un segment de codi en què un fil utilitza recursos (com certes variables d'instància) que altres fils poden utilitzar, però no ho poden fer alhora.
Sobrecàrrega (mètodes: Overloading)
Definició de dos mètodes amb el mateix nom i classe però diferents implementacions.
Sobreescriptura (mètodes: Overriding)
Quan una subclasse ofereix una implementació específica que ja s'ha oferit a una superclasse.
Subclasse
Classe que hereta. També classe fill.
Superclasse
Classe heretada. També classe pare.
Synchronized
Una paraula clau en el llenguatge de programació Java que, quan s'aplica a un mètode o bloc de codi, garanteix que, com a molt, un fil alhora executi aquest codi.
Tipus genèric
Un tipus genèric és una classe o interfície amb una secció de tipus de paràmetres envoltada per l'operador diamant <>.
Tipus primitiu
Un tipus de variable on el seu valor té una mida i format en concordança amb el seu tipus.
Tipus cru (raw)
Un tipus cru és una classe o interfície genèrica utilitzada sense paràmetres de tipus. No es recomana el seu ús.
Variable
Una dada amb un identificador. Té un tipus i un àmbit. Hi ha variables de classe, d’instància i locals. Poden ser de tipus primitiu o referència.
Variable d'instància
Una variable associada amb un objecte particular. També es diu “camp”.
Variable de classe
Una variable associada amb una classe, però amb cap instància particular. També es diu “camp estàtic”.
Variable local
Una variable coneguda dins d’un bloc, com per exemple dins d’un mètode.
Eines Java
junit
JUnit permet automatitzar les proves de codi en Java.
Una classe de proves en Java permet l'execució de proves unitàries sobre el codi que volem validar. A JUnit (5) podem indicar que una classe és de proves utilitzant anotacions davant dels mètodes d'aquesta classe. Principalment:
- @BeforeAll: mètode estàtic que s'executarà abans de totes les proves.
- @BeforeEach: mètode que s'executarà abans de cada prova.
- @Test: anotació més important, indica que el mètode és una prova a executar.
- @AfterEach: mètode que s'executarà després de cada prova.
- @AfterAll: mètode que s'executarà després de totes les proves.
Una prova (test) té un codi que pot fallar. Si no falla, la prova és correcta. Pot fallar per dues raons:
- Que hi hagi una excepció durant l'execució de la prova que no es gestioni al codi. Es comptabilitzen com a error.
- Que s'utilitzi una asseveració (assertion) de la llibreria JUnit, i que aquesta no es compleixi. Es comptabilitzen com a fallada (failure).
Les asseveracions de JUnit són crides a mètodes estàtics que poden resoldre's amb aquest import (JUnit 5):
import static org.junit.jupiter.api.Assertions.*;
Aquestes són algunes de les asseveracions més importants:
- assertTrue(boolean): falla si el paràmetre és false
- assertFalse(boolean): falla si el paràmetre és true
- assertEquals(object1, object2): falla si els paràmetres no són iguals
- assertNotEquals(object1, object2): falla si els paràmetres són iguals
- fail(): falla sempre
Totes aquestes asseveracions permeten afegir un text explicatiu per ajudar-nos a entendre el problema que s'ha produït.
Els IDE moderns detecten quan una classe és de prova gràcies a que conté anotacions de @Test, i llavors permeten executar aquestes proves visualment, fent el recompte d'errors i permetent identificar en quina part del codi s'han produït.
A continuació es poden veure dues captures d'un error i una fallada a Eclipse.
Error
Hi ha una excepció a la prova testAddCredit(). La Failure Trace mostra quina excepció ha passat (primera línia) i la línia del codi on ha passat (ShopClientImpl.java).
Fallada
Hi ha asseveració que falla a la prova testJustCredit(). La Failure Trace mostra el missatge fallit de l'asseveració (primera línia), la línia del test on ha passat (ShopCartClientTest.java).
maven
Maven és una eina que permet gestionar la construcció i desplegament automàtics de projectes Java. Mitjançant un arxiu pom.xml
, es descriu el procés de construcció i quines són les dependències d'altres projectes o llibreries. Aquestes dependències són automàticament descarregades d'un repositori central per ser utilitzades localment.
Maven té tres lifecycles predefinits:
- default: gestiona el desplegament
- clean: gestiona la neteja
- site: gestiona la documentació
Cada lifecycle conté una sèrie de fases consecutives. En particular, el default conté principalment:
- validate: valida la informació del projecte
- compile: compila el codi font
- test: proves unitàries del codi font
- package: empaqueta el codi font per a ser distribuït, habitualment JAR
- verify: proves d'integració
- install: instal.lació del paquet al repositori local (dependències locals)
- deploy: copiar el paquet a un repositori remot
L'executable de maven es diu mvn
. Una línia de comanda típica seria:
$ mvn clean install
que executa les fases clean (lifecycle clean) i després install (lifecycle default). La fase install executa cadascuna de les fases anteriors del mateix lifecycle: validate, compile, test, package, verify i install. Aquestes fases tenen els seus goals associats en funció del packaging escollit.
Cada fase està feta d'una sèrie d'objectius o goals, que són tasques a executar. Per exemple, la fase compile està associada al plugin compiler i el goal compile. Hi ha goals que no estan associats a cap fase. Per exemple, hi ha el plugin exec-maven-plugin, que permet executar una classe Java. El plugin es diu exec, i el goal seria java. Per dir-li quina classe executar, cal indicar-ho amb la propietat exec.mainClass:
$ mvn exec:java -Dexec.mainClass="com.example.Main"
Maven utilitza una estructura de directoris predefinida:
/my-app
/src/main
/java => codi
/resources => recursos
/src/test
/java => proves
/resources => recursos de les proves
/target => arxius generats
/pom.xml => configuració
Aquest és el format típic d'un pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>poo_psp</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
<name>common</name>
<url>https://example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<version>1.1.4</version>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- afegir referències a plugins -->
</plugins>
</build>
</project>
Explicació d'algunes seccions:
- groupId + artifactId identifiquen el paquet.
- La versió és 1.0-SNAPSHOT, SNAPSHOT indica que el codi no és estable (darrera versió).
- El packaging és JAR. Podria ser també WAR o EAR, per altres tipus d'entorns.
- Les properties permeten indicar la codificació dels arxius font (UTF-8) i la versió de java (11).
- La secció dependencies permet definir quines llibreries necessitarem perquè maven les descarregui i les utilitzi en els lifecycles. Indica sempre el groupId + artifactId (clau) i la versió.
- La secció build permet fer referència a plugins addicionals. Poden configurar-se amb la corresponent subsecció configuration (parameter), o bé a la secció general properties (user property).
Els paquets poden buscar-se a https://mvnrepository.com/, on podem trobar la sintaxi per a afegir-los a les dependencies. Aquí també podem veure les dependències de cada paquet, si les té, que també seran descarregades.
jps i jstack
Els podeu trobar a la vostra instal.lació del JDK, a la carpeta BIN on hi ha el java i el javac. JPS permet fer una llista dels processos java que s’estan executant:
$ jps
1648 org.eclipse.equinox.launcher_1.5.800.v20200727-1323.jar
6240 Main
5608 Jps
8636 DeadlockTest
Amb JSTACK podeu veure el contingut de les piles d’execució dels fils. Imagineu que DeadlockTest té dos fils, un que es diu loop i l’altre wait (s’han suprimit els fils que no interessen):
$ jps 8636
…
"wait" #12 prio=5 os_prio=0 tid=0x000002585443e000 nid=0x9d8 waiting for monitor entry [0x000000f255aff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at m09uf2.test.DeadlockTest.lambda$3(DeadlockTest.java:85)
- waiting to lock <0x000000076b610938> (a m09uf2.test.DeadlockTest$MLong)
at m09uf2.test.DeadlockTest$$Lambda$2/245257410.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"loop" #11 prio=5 os_prio=0 tid=0x000002585443d000 nid=0x29f8 waiting on condition [0x000000f2559fe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at m09uf2.test.DeadlockTest.lambda$2(DeadlockTest.java:71)
- locked <0x000000076b610938> (a m09uf2.test.DeadlockTest$MLong)
at m09uf2.test.DeadlockTest$$Lambda$1/1044036744.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
…
Es pot veure l'estat i la línia on s'estan executant: BLOCKED (:85) i TIMED_WAITING (:71).
visualvm
El podeu instal.lar des de: https://visualvm.github.io/
Si l’executeu, i fent doble clic sobre el procés que voleu mirar, veureu:
Podeu veure diferents pestanyes:
- Monitor: mostra CPU, espai de memòria utilitzat (heap/metaspace), classes i fils.
- Threads: visualització gràfica dels fils que s’executen i l’estat en colors.
- Sampler: rendiment CPU i memòria de l’aplicació.
Web
Conceptes d'HTML
Entendre com funciona l'HTML amb JavaScript és fonamental quan es construeixen aplicacions web. En aquesta guia, cobrirem l'estructura d'HTML, una llista més profunda d'etiquetes, com es carrega l'HTML en un navegador, la importància de l'etiqueta <script>
, els atributs i propietats, i més.
Estructura i càrrega
L'HTML (HyperText Markup Language) estructura el contingut d'una pàgina web. Els navegadors analitzen l'HTML per mostrar el contingut, des de text i imatges fins a formularis i botons. Aquí tens com funciona l'estructura bàsica d'HTML:
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>La Meva Pàgina Web</title>
</head>
<body>
<h1>Benvingut!</h1>
<p>Això és un paràgraf de text.</p>
</body>
</html>
<!DOCTYPE html>
: Indica al navegador que aquest document és HTML5.<html>
: L'element arrel de tota la pàgina web.<head>
: Conté metadades, inclòs el títol de la pàgina, el conjunt de caràcters, i recursos externs com fulls d'estil o scripts.<body>
: Conté el contingut real que veuen els usuaris, com ara text, imatges i botons.
Quan obres un document HTML, el navegador el processa seguint aquests passos:
- Analitzar l'HTML: El navegador llegeix l'HTML, començant des de dalt. Construeix un arbre DOM (Document Object Model), una representació estructurada del document.
- Carregar recursos externs: A mesura que llegeix l'HTML, el navegador pot trobar enllaços a altres recursos com fulls d'estil CSS o scripts JavaScript i comença a carregar-los en paral·lel.
- Renderitzar el contingut: Quan l'arbre DOM està completament construït i els recursos externs han estat carregats, el navegador mostra la pàgina web.
Etiqueta script
La posició de l'etiqueta <script>
dins del fitxer HTML és important perquè influeix en la rapidesa amb què apareix el contingut i en com interactuen els scripts amb el DOM.
Millors pràctiques:
- Col·locar els scripts abans del tancament del cos: Molt sovint, és millor col·locar les etiquetes
<script>
just abans de l'etiqueta perquè el contingut HTML es carregui primer i, després, el JavaScript.
<body>
<h1>La Meva Pàgina Web</h1>
<script src="script.js"></script>
</body>
-
Utilitzar els atributs defer o async: Aquests atributs ajuden a millorar el rendiment de la càrrega.
- defer: Garanteix que el script s'executa després que l'HTML estigui completament analitzat. És útil per a scripts que depenen que els elements del DOM estiguin preparats. No obliga situar el script després del body html, que fins i tot pot estar al head.
<script src="script.js" defer></script>
- async: Carrega el script de manera asíncrona i l'executa tan aviat com es descarrega, sense esperar que l'HTML acabi de carregar-se. Utilitza aquest atribut per a scripts que no depenen del DOM.
<script src="script.js" async></script>
Elements HTML
Els elements HTML representen diferents tipus de contingut. Tenen una especificació HTML i una altra relacionada amb el DOM. Per exemple, l'especificació de l'etiqueta <input>
està relacionada amb la de HTMLInputElement.
La majoria d'etiquetes permeten tenir contingut en el seu cos, i llavors tenen una etiqueta d'inici i una altra de final. Com per exemple, <div>content</div>
. També tenim etiquetes de tipus void, que no permeten contingut intern. Per exemple, <img src="https://someurl">
. Aquestes no tenen etiqueta de final, tot i que es tolera l'ús de l'etiqueta de self-closing: <img />
. Altres etiquetes void: <br>
, <hr>
, <input>
, <link>
, <meta>
.
Cada etiqueta té atributs i propietats específiques que modifiquen el comportament de l'element.
Els Atributs: formen part de l'etiquetatge HTML i defineixen valors o configuracions inicials quan es carrega la pàgina. Són estàtics i no canvien tret que es modifiquin explícitament a l'HTML o JavaScript.
Aquests són alguns atributs molt comuns:
- id: assigna un identificador únic a un element. S'utilitza per a estils (CSS) i scripts (JavaScript) per a dirigir-se a elements específics.
- class: assigna un o més noms de classe a un element. Les classes es poden compartir entre diversos elements per a l'estil de grup o la creació de seqüències de comandaments.
- style: especifica estils CSS en línia per a un element. Tot i que normalment es recomana utilitzar fulls d'estil externs o interns, els estils en línia poden ser útils per a l'estil dinàmic.
- data-*: s'utilitza per emmagatzemar atributs de dades personalitzats en un element. Això s'utilitza sovint juntament amb JavaScript per emmagatzemar o manipular dades al DOM.
Les Propietats: pertanyen al DOM (Document Object Model) i reflecteixen l'estat actual de l'element. Es poden actualitzar dinàmicament o accedir-hi mitjançant JavaScript.
Aquestes són algunes propietats molt comunes:
- id: reflecteix l'atribut del mateix nom.
- innerHTML: representa el contingut HTML dins d'un element. Podeu utilitzar aquesta propietat per obtenir o establir HTML dinàmicament dins d'un element. Aplicable a
<p>
,<div>
,<span>
, etc. - textContent: represents the text content inside an element, without any HTML tags. It's often used for retrieving or setting plain text. Aplicable a
<p>
,<div>
,<span>
, etc. - innerText: representa el contingut de text dins d'un element, de manera similar a textContent, però amb una diferència clau:
innerText
respecta l'estil CSS (comdisplay: none
), de manera que només retorna text visible. També permet establir text sense format dins d'un element. Aplicable a<p>
,<div>
,<span>
, etc. - style: es pot accedir a aquesta propietat o modificar-la mitjançant JavaScript per canviar dinàmicament les propietats CSS de l'element.
- classList: proporciona mètodes per afegir, eliminar o canviar classes en un element. És útil per manipular classes dinàmicament.
- value: reflecteix el valor dels elements relacionats amb el formulari com
<input>
,<textarea>
,<select>
i<button>
. Aquesta propietat s'utilitza per obtenir o establir el valor actual als camps d'entrada de l'usuari. - disabled: propietat booleana que serveix per desactivar la interacció dels elements relacionats amb el formulari.
JavaScript modern
- Javascript bàsic
- Funcions
- Execution contexts i event loop
- Orientació a objecte
- Javascript asíncron
- Modern things
- Referències
Javascript bàsic
Javascript és un llenguatge d'alt nivell que utilitza compilació just-in-time, o sigui, es compila durant la seva execució.
Javascript va començar com un llenguatge que permetia petites interaccions a les pàgines web, però esdevé important a partir de 2015 (versió ES6), quan comencen a fer-se revisions anuals de l'estàndard. ECMAScript (o ECMA-262) és el standard que fa interoperable Javascript als navegadors i al servidor (nodejs).
A la versió 2009 (ES5) es va introduir strict mode. Fins llavors, diem que utilitzàvem Javascript tradicional. El desenvolupament modern es fa exclusivament utilitzant aquest mode, ja que permet evitar molts bugs en ser més estricte: elimina errors silenciosos i prohibeix algunes sintaxis. Està activat per defecte en alguns casos (mòduls i objectes), però pot activar-se amb 'use strict' com a primera línia de codi d'un arxiu.
Variables
Javascript és un llenguatge de tipatge dinàmic: el tipus està associat al valor, no a la variable. Es pot canviar el valor d'una variable, i per tant el seu tipus.
A JS tradicional s'utilitza var
per declarar variables. No cal inicialitzar, i llavors la variable té el valor undefined
. La variable té l'àmbit de la funció on es declara.
A JS modern s'utilitzen let
i const
per a declarar variables i constants. La variable té l'àmbit del bloc on es declara.
Primitius
Els tipus de Javascript són els primitius i els objectes.
Els primitius essencials són:
boolean
:true
ofalse
.null
: una variable assignada intencionalment a res.undefined
: una variable mai assignada.- numèrics: números de doble precisió.
NaN
és un valor numèric no representable. - strings.
Els tipus primitius són immutables: no es poden modificar un cop creats. En canvi, els objectes (inclosos els arrays) són mutables. També se'ls diu tipus referència. Quan es comparen, són iguals si fan referència al mateix objecte. I quan s'assignen a una variable estem fent una nova referència al mateix objecte. Les còpies d'objectes poden ser superficials (referència) o profundes.
Conversions i operadors
Conversions:
- Podem fer conversions amb
Boolean(valor)
,Number(valor)
iString(valor)
- A número:
undefined
ésNaN
,false
inull
és0
,true
és1
string
a número: si buit,0
, si no, s'intenta llegir (NaN
en cas d'error)- A
boolean
: sonfalse
els valors0
,string
buit, els nullish (null
iundefined
) iNaN
(falsy values) i la restatrue
(truthy values)
Operadors:
- Els operador matemàtics són
+ - * / % i **
- També hi ha l'operador concatenació per a strings
+
- L'operador
+
unari equival aNumber(valor)
- L'assignació
=
és també un operador, i retorna el valor de l'expressió - Operadors modify-in-place per a tots els operadors aritmètics
+= -= ...
- Increment/decrement
++ --
- Operadors de bits
& | ^ ~ << >> >>>
- Operador coma retorna l'últim valor avaluat
- Operador condicional
? condition? value1 : value2
retorna value1 si la condició éstrue
||
troba el primer valor truthy&&
troba el primer valor falsy??
retorna el primer valor definit (nulllish coaslescing)?.
retornaundefined
si la propietat o mètode a continuació és nullish (optional chaining)??=
només assigna a si l'operand a l'esquerra és nullish (nullish coalescing assignment)
Comparacions:
- Els operadors habituals
> < >= <= != ==
(equals)===
(strict equals) - "Strict equals" no fa conversió per a comparar
Objectes
Els objectes són col.leccions de propietats accessibles mitjançant claus. Les propietats poden tenir qualsevol tipus. És una organització de tipus mapa.
Els objectes es poden crear amb la notació literal { clau1: valor1, clau2: valor2...}
.
Alguns objectes existents al llenguatge:
- Dates: representació de dates.
- Arrays: propietats accessibles amb un índex.
- Basats en claus: Maps, Sets.
La paradoxa dels primitius string
, number
i boolean
és que també permeten mètodes utilitzant embolcalls anomenats String
, Number
i Boolean
. Només es creen quan es criden els mètodes.
Els strings tenen aquests mètodes essencials: trim()
, includes()
, indexOf()
, toUpperCase()
, toLowerCase()
, replace()
, slice()
, split()
, repeat()
, match()
, charAt()
, charCodeAt()
.
Quant als números: parseInt()
, toString()
, toExponential()
, toFixed()
, toPrecision()
, valueOf()
, toLocaleString()
, parseFloat()
, isInteger()
.
Els arrays són un tipus d'objecte que poden canviar de mida i contenir diferents tipus de dades. Estan indexats per sencers i permeten diverses operacions:
push(e)
: afegeix un element al final de l'array.shift()
ipop()
: extreu i retorna el primer / l'últim element de l'array,undefined
si està buit.concat(arr1, ...)
: crea un nou array amb els continguts de dos o més arrays.slice(start, end)
: crea una còpia superficial d'una part d'un array (end, primer índex a excloure, és opcional).splice(start, deleteCount, item1, ...)
: canvia el contingut d'un array esborrant o substituint.
Operacions iteratives:
map(fn(e){...})
: crea un nou array amb els valors retornats per la funció.find((testFn(e){...})
: retorna el primer element que satisfà la funció de prova.findIndex(testFn(e){...})
: retorna l'índex del primer element que satisfà la funció de prova.filter(testFn(e){...})
: crea una còpia superficial del array amb els elements que satisfan la funció de prova.reduce(reducerFn(prev,e){}, initial)
: retorna un valor únic iterant pels elements i amb un valor previ. El primerprevi
ésinitial
.
Comprovacions de tipus
L'operador typeof s'utilitza per determinar el tipus primitiu d'una variable. Retorna una cadena amb el nom del tipus, com ara string
, number
, boolean
, object
, undefined
, function
, or symbol
.
console.log(typeof "hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof {}); // "object"
console.log(typeof undefined); // "undefined"
console.log(typeof function(){}); // "function"
L'operador instanceof comprova si un objecte és una instància d'una classe o funció constructora específica, retornant vertader o fals. És especialment útil per comprovar classes personalitzades, instàncies d'arrays i objectes de llibreria.
class Dog {}
const myDog = new Dog();
console.log(myDog instanceof Dog); // true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true (arrays are objects in JavaScript)
Iteració
Podem iterar sobre els arrays i els objectes.
array.forEach(fn(item, index))
per a iterar sobre arrays.for...of
obté els valors per a iterables, com els arrays, strings, Map, Set i NodeList.for...in
per a propietats enumerables d'objectes.Object.keys(obj)
iObject.values()
retornen arrays amb propietats i valors, respectivament.
Hoisting
El hoisting és un procés de l'intèrpret JS on sembla moure la declaració de variables, funcions i classes a dalt del seu àmbit abans d'executar el codi. Això permet, per exemple, utilitzar funcions abans de declarar-les. També es pot fer amb declaracions de variables i classes, però en general no es recomana.
Funcions
Les funcions són objectes que poden ser cridats. La declaració d'una funció té aquest aspecte:
function name(parameter1, parameter2, parameter3 = "some default value", ...) {
// body
}
Els paràmetres es comporten com variables arguments. Quan cridem la funció li passem arguments. Els arguments no definits en la crida tenen el valor undefined
. Els paràmetres per defecte s'avaluen només si no s'indica l'argument corresponent.
El valor de retorn d'una funció que no retorna res és undefined
.
Les variables locals són visibles només dins la funció. Les variables externes (o globals) també són accessibles. Si es diuen igual que alguna local, llavors perden l'accessibilitat.
Expressions de funcions
Les funcions admeten expressions. Per exemple, dirHola
és una expressió de funció:
let dirHola = function(nom) {
console.log(`hola, ${nom}`);
};
dirHola('Joan');
function fesCrida(laMevaFuncio, nom) {
laMevaFuncio(nom);
}
fesCrida(dirHola, 'Anna');
La crida dins de fesCrida
es fa exactament a la mateixa funció dirHola
. El paràmetre laMevaFuncio
té un valor de tipus funció i s'anomena callback, ja que permet cridar de tornada un codi.
Les declaracions de funcions es poden cridar abans de ser creades (hoisting), i només són visibles al block a que pertanyen. Les expressions de funcions només es poden cridar un cop assignades.
Funcions fletxa
Les funcions fletxa són una alternativa a les declaracions de funció que hem vist.
let func1 = (arg1, arg2, ...) => expression;
let func2 = (arg1, arg2, ...) => sentence;
let func3 = (arg1, arg2, ...) => {
// code with optional return sentence
}
Aquí func1
és una funció que retorna una expressió. Per exemple:
let suma = (a, b) => a + b;
suma(1,2);
En canvi func2
podria no retornar res, per exemple:
let logme = (a) => console.log(a);
logme(somevar);
Finalment func3
respon a la necessitat d'una funció multilínia.
Rest & Spread
Quan volem passar un nombre variable d'arguments podem utilitzar la sintaxi "resta de paràmetres", que sempre va al final dels paràmetres d'una funció:
function someFunc(arg1, arg2, ...args) {
// ... aquí args és un array: args[0], args[1]...
}
L'acció inversa ens permetria passar un array com a paràmetres individuals. Per exemple, si volem utilitzar Math.max(arg1, arg2, ... argN)
podem fer-ho així:
let arr = [1, 4, 9];
console.log(Math.max(...arr));
La sintaxi "spread" permet fer còpies d'arrays i d'objectes. Per exemple:
let arr = [1, 4, 9];
let arrCopy = [...arr];
let obj = { a: 1, b: 4, c: 9 }
let objCopy = {...obj};
let ObjCopyChanged = {...obj, a: 3} // a canvia a 3 (després de copiar 1)
Execution contexts i event loop
Les crides al Web API fan que hi hagi callbacks que s'hagin de cridar en certs moments (esdeveniments). Quan és així, la Web API afegeix aquestes crides a una cua de callbacks. L'event loop comprovarà quan està buit el stack per anar traient les callbacks i executant-les. En resum, JavaScript s'executa en un sol fil, i les callbacks ho fan quan el stack està buit.
Orientació a objecte
Un objecte es pot crear amb un inicialitzador:
const name = 'John';
const person = {
name: name,
getName: function() {
return this.name;
}
};
Una classe és una plantilla per a crear un objecte. Utilitzen internament funcions constructors i prototips.
class Person {
constructor (name) {
this.name = name;
}
getName() {
return this.name;
}
}
const person = new Person('Ann');
console.log(person.getName());
Aquesta seria l'alternativa amb funció constructor i prototips:
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
Podem estendre una classe amb:
class JohnPerson extends Person {
constructor() {
super('John');
}
}
this
Només ens referirem al mode estricte.
- En el context global, this és undefined.
- En el context d'una funció, depèn de com es va cridar la funció:
- Cap valor, quan s'executa una funció aïllada.
- Al constructor d'una classe, la nova instància.
- A un mètode
objecte.metode()
, l'objecte que el crida. - A una funció fletxa, el this del context pare.
Per al primer cas (1), hi ha una forma d'associar una funció a una instància: utilitzant el mètode bind(instància)
de la funció es retornarà una nova funció que pot cridar-se aïlladament i on this
és la instància.
Aquí, la crida a boundMyFunc
assignarà this = myObj dins la meva funció:
function myFunc() {
// ...
}
let boundMyFunc = myFunc.bind(myObj);
boundMyFunc();
JSON
JSON és una representació en cadena (string) d'una estructura de dades que permet intercanviar-les fàcilment. La representació arrel pot ser un objecte {}
o bé un array []
, que poden contenir altres objectes, altres arrays o bé els tipus essencials: string, number, true/false o null.
JavaScript té dos mètodes que permeten convertir un objecte o array a un string JSON i també l'operació inversa:
- JSON.stringify(valor)
- JSON.parse(string)
A diferència dels literals JavaScript, la representació JSON sempre utilitza dobles cometes per als strings. A més, totes les propietats també han de tenir dobles cometes.
JSON.stringify no converteix qualsevol valor. Per exemple, no ho fa amb funcions o altres objectes, com Map. De vegades, cal implementar el segon i tercer paràmetres opcionals, replacer and reviver, per soportar certs tipus.
Javascript asíncron
Callbacks
L'API de JavaScript al navegador permet programar accions asíncrones. Per exemple, coses a fer quan hi ha un esdeveniment, o temporitzadors que executen altres accions. Per realitzar-les s'utilitzen callbacks, o sigui, valors de tipus funció.
Per exemple, setTimeout(callback, milliseconds)
permet cridar una funció després d'un cert temps. Aquesta callback pot ser, per exemple, una funció fletxa:
setTimeout(() => {
console.log("ha passat 1 segon");
}, "1000")
Si volem executar una callback dins d'una altra es pot produir la "piràmide de la perdició": una sèrie de funcions a dins d'altres funcions imbricades que fan el codi poc llegible. Per això van aparèixer les promeses.
Promises
El codi asíncron té habitualment dues parts:
- Un codi de producció que triga un cert temps a executar-se.
- Un codi de consumició que necessitarà allò produït un cop estigui llest.
Productor
Una promesa ajunta aquestes dues parts.
let promesa = new Promise(function(resolve, reject) {
// codi de producció
});
El codi de producció ha de fer una d'aquestes dues crides un cop acabi:
resolve(value)
per indicar que tot va anar bé i el resultat ésvalue
.reject(error)
per indicar que no va anar bé i l'error éserror
.
Per exemple, una promesa que acaba quan passa 1 segon amb el resultat "fet":
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("fet"), 1000);
// error podria ser: reject(new Error("hi ha un error"))
});
Consumidor
Podem utilitzar el mètode then
d'una promesa per a consumir-la:
promise.then(
function(result) { /* consumeix el resultat correcte */ },
function(error) { /* consumeix l'error */ }
);
// per exemple, per cridar alert amb el resultat (callback error no utilitzada):
promise.then(alert);
// es poden encadenar then, catch i finally:
promise.then(consumidorResultat).catch(consumidorError).finally(cridaSempre);
Si el consumidor del resultat retorna una promesa, es poden encadenar thens:
promise.then(value => {
return value.anotherPromise();
}).then(anotherValue => {
// use anotherValue
});
Si el que es retorna no és una promesa, l'API la converteix automàticament a promesa que resol al valor retornat:
const promise1 = new Promise((resolve, reject) => resolve(1));
promise1.then(v1 => v1 + 1).then(v2 => v2 *2).then(v3 => console.log(v3));
Es poden afegir diversos consumidors a un productor fent thens a la mateixa promesa:
promise.then(consumidorResultat1);
promise.then(consumidorResultat2);
Async/Await
Les promeses es poden utilitzar amb una sintaxi alternativa més amigable.
Per exemple, per dir que una funció retorna una promesa només cal prefixar-la amb async
:
async function myAsyncFunc() {
return 1; // el mateix que Promise.resolve(1)
}
myAsyncFunc().then(alert); // mostra 1
Compte, perquè myAsyncFunc
s'executarà de forma asíncrona ja que retorna una promesa. Per tant, si fem:
async function myAsyncFunc() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 3000)
});
}
myAsyncFunc(); // retorna la promesa, que no utilitzem
console.log('crida feta!');
primer es mostrarà "crida feta!" i als tres segons es resoldrà la promesa, que ningú espera (no hi ha then).
Per altra banda, await
espera fins que una promesa es resol i retorna el seu resultat. Només es pot utilitzar dins d'una funció prefixada amb async
(també a mòduls):
async function myAsyncFunc() {
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 3000)
});
const value = await promise;
return "well " + value;
}
myAsyncFunc().then(value => alert(value));
Modern things
Modules
Els mòduls a JavaScript són una forma de trossejar el codi per a trossejar-lo i fer-lo més fàcil de gestionar. Inicialment, el patró IIFE permetia crear mòduls. Altres frameworks han implementat aquest patró, com per exemple CommonJS en el cas de NodeJS. A partir d'ES6 tenim una implementació a la especificació.
Per crear un mòdul cal exportar des del mòdul A i importar-lo des del mòdul B. Hi ha dos tipus d'exports:
- default:
export default ...;
. S'importen ambimport someNameOfYourChoice from './path/to/file.js';
. - named:
export const someData = ...;
. S'importen ambimport { someData } from './path/to/file.js';
.
Un arxiu només pot contenir un mòdul default i un nombre il.limitat d'anomenats.
També es poden importar els anomenats amb import * as upToYou from './path/to/file.js';
i llavors pots accedir als elements exportats com upToYou.someData
.
Template literals
També anomenada "string interpolation", una cadena amb backticks permet incloure el resultat d'expressions:
let cadena = `bon dia, ${name}!`;
Shorthand syntax
La declaració shorthand permet assignar propietats d'un objecte on la clau i el valor tenen el mateix identificador, evitant haver de repetir ident: ident
.
const name = 'Pere'
const age = 20
const location = 'Sabadell'
const user = {
name,
age,
location
}
Computed property names
Podem avaluar els noms de les propietats d'un objecte de forma dinàmica. Per exemple:
const obj = {
[someExpression]: value
}
Array & Object destructuring
El "destructuring" permet desempaquetar arrays i objectes en variables.
let arr = ["John", "Smith"];
let [firstName, surname] = arr;
A la dreta pot haver qualsevol dada iterable. A l'esquerra pot haver qualsevol assignable, per exemple:
let user = {};
[user.name, user.surname] = "John Smith".split(' ');
Si l'array és més llarg que la llista a l'esquerra, els ítems extra s'ignoren. Si l'array és més curt, els ítems de l'esquerra seran undefined
. També podem ignorar ítems de la dreta escrivint comes seguides. I també podem utilitzar spread per a recollir la resta:
let [name1, name2, ...rest] = ["Rosa", "Joan", "Anna", "Pere"];
[user.name, user.surname] = "John Smith".split(' ');
Amb els objectes també ho podem fer:
let obj = {var1: "valor1", var2: "valor2"};
let {var1, var2} = obj;
// no hi ha ordre a les propietats!
Podem reanomenar una propietat i utilitzar valors per defecte. Seguint l'exemple anterior:
let obj = {var1: "valor1", var2: "valor2"};
let {var1: valor1, var2: valor2, var3 = "valor3"} = obj;
// "valor3" podria ser qualsevol expressió
Podem només extreure la part que ens interessa:
let obj = {var1: "valor1", var2: "valor2"};
let {var1} = obj;
Podem utilitzar la sintaxi "resta":
let obj = {var1: "valor1", var2: "valor2", var3: "valor3"};
let {var1, ...rest} = obj;
// rest serà un objecte amb dos propietats, var2 i var3
Podem fer destructuring anidat:
let obj1 = {
var1: {
opt1: 1,
opt2: 2
},
var2: "valor2",
var3: ["first", "second", "third"]
};
let {
var1: { opt1, opt2 },
var3: [first, ...rest] } = obj1;
Podem utilitzar-ho per a passar paràmetres de funcions:
let obj = {var1: "valor1", var2: "valor2", var3: "valor3"};
function myFunc1({var1, var2}) {
// ...
}
myFunc1(obj);
let arr = ["first", "second"];
function myFunc2([var1, var2]) {
// ...
}
myFunc2(arr);
Referències
- javascript.info
- Client-Side Web Development
- Eloquent JavaScript
- Patterns
- ECMA-262
- Introducing JavaScript objects
- this, MDN
- What the heck is the event loop anyway? Philip Roberts
- Loupe
- Javascript Visualizer
Manipulació del DOM
El DOM (Document Object Model) és una representació d'un document HTML com a una estructura d'arbre, on cada node és un objecte que representa una part del document. El DOM es crea quan es carrega una pàgina web, i és manipulable des de JavaScript gràcies a una sèrie d'operacions que permeten:
- Afegir, modificar i esborrar qualsevol element o atribut HTML.
- Canviar qualsevol estil CSS.
- Reaccionar a un esdeveniment.
- Crear nous esdeveniments.
Navegació
Imaginem que tenim aquest senzill document:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Simple DOM example</title>
</head>
<body>
<section>
<img
src="dinosaur.png"
alt="A red Tyrannosaurus Rex" />
<p>
A link to the
<a href="https://www.mozilla.org/">Mozilla homepage</a>
</p>
</section>
</body>
</html>
Per poder manipular el DOM primer cal obtenir una referència a l'element i desar-la a una variable. Podem utilitzar Document.querySelector(), que demana un selector CSS com a paràmetre:
Per exemple:
const link = document.querySelector("a");
També tenim Document.querySelectorAll() per a obtenir una llista d'elements en format NodeList
:
const paragraphs = document.querySelectorAll("p");
paragraphs.forEach((paragraph) => {
// do something with it
});
Tot i que amb aquests dos mètodes en tenim prou per fer queries, hi ha altres (menys flexibles) com Document.getElementById()
, Document.getElementsByTagName()
, Document.getElementsByClassName()
, etc. També podem veure els elements fill amb la propietat children
, que retorna una col.lecció HTMLCollection
.
Si volem moure'ns cap a dalt, podem utilitzar la propietat parentElement
o el mètode closest(selector)
. També ens podem moure cap als costats amb nextElementSibling
i previousElementSibling
.
Selectors CSS
Aquests són els selectors més habituals:
- Tots:
*
- Etiqueta:
head
- Classe:
.red
- ID:
#nav
- Etiqueta i classe:
div.row
- Valor d'atribut:
[aria-hidden="true"]
- Fills d'un altre element:
li a
- Fills directes:
li > a
- Tots dos selectors:
li, a
Pseudo-selectors:
- Primer fill:
:first-child
- Últim fill:
:last-child
- Element amb hover o focus:
:hover
,:focus
- Element clicat:
:active
- Enllaços no clicats o clicats:
:link
,:visited
Modificació
També podem afegir nous nodes al DOM. Si es volgués afegir un paràgraf a la secció seria així:
const sect = document.querySelector("section");
const para = document.createElement("p");
para.textContent = "We hope you enjoyed the ride.";
sect.appendChild(para);
També podem esborrar un element de diferents formes:
sect.removeChild(para);
para.remove();
para.parentNode.removeChild(para);
Una altra opció és utilitzar un template de l'html i clonar-lo al contingut.
<main class="container">
<h1>Template!</h1>
</main>
<template id="message">
<section>
<h2 class="heading"></h2>
<p class="text"></p>
</section>
</template>
const template = document.querySelector("#message");
const message = template.content.cloneNode(true);
message.querySelector('.heading').textContent = 'A title';
message.querySelector('.text').textContent = 'Some text here';
const main = document.querySelector('main');
main.appendChild(message);
Atributs i propietats
Els elements tenen atributs i propietats:
- Els atributs són les característiques dels elements que apareixen dins de l'etiqueta, i es defineixen a l'HTML. S'anomenen amb paraules separades per guions. Exemple:
src
,alt
. - Les propietats defineixen el comportament intern i la funcionalitat d'un element, i es manipulen des de JS. S'anomenen amb camel case. Exemple:
value
oinnerHTML
.
Tenim Element.getAttribute()
, Element.setAttribute()
, Element.removeAttribute()
i Element.hasAttribute()
per a gestionar els atributs d'un element. Les propietats, en canvi, es manipulen com a propietats de l'objecte.
Podem tenir atributs que tenen una representació com a propietats. Per exemple, id
és un atribut i propietat. Normalment es mantenen sincronitzats:
p.setAttribute("id", "one");
let id1 = p.getAttribute("id"); // one
let id2 = p.id; // one
En canvi, les propietats d'un formulari modificables per l'usuari (value
, checked
, selected
) no estan sincronitzades: l'atribut és el valor inicial de l'HTML i la propietat, l'actual. De fet, Document.setAttribute()
només canvia el valor si no ho ha fet l'usuari, però value
ho fa sempre.
La manipulació d'estils es realitza accedint a la propietat HTMLElement.style
. També és molt útil la propietat Element.classList
, que retorna una DOMTokenList
. Aquesta permet afegir i esborrar classes d'un element amb els mètodes add
i remove
, per exemple.
Atributs data
Val la pena parlar dels atributs data. Se solen utilitzar des de JS, en contraposició als atributs HTML, ja que no tenen significat a l'hora de visualitzar un element.
<div class="expand" data-expand>
<p>Some content that can be collapsed or expanded.</p>
<button data-click="sayHi">Say Hello!</button>
<button data-click="showMore">Show More</button>
</div>
let accordion = document.querySelector('[data-expand]');
let btnHi = document.querySelector('[data-click="sayHi"]');
let btnMore = document.querySelector('[data-click="showMore"]');
En general, si volem manipular amb JS els elements del DOM, és preferible utilitzar atributs data, que no condicionen la creació d'IDs o classes, que habitualment associem als estils CSS.
Esdeveniments
Podem afegir esdeveniments associats a un node del DOM utilitzant EventTarget.addEventListener()
. La sintaxi més habitual inclou dos paràmetes:
- El
type
indica quin tipus d'esdeveniment vol escoltar-se. - El listener sol ser una funció callback que rep un objecte Event. Aquest objecte té tres propietats interessants:
type
, el tipus d'esdeveniment;target
, l'element que ha generat l'esdeveniment; icurrentTarget
, el que té associat el listener. Aquestes dues últimes poden ser diferents perquè els esdeveniments són bombolles que pugen.
Exemple de l'efecte bombolla.
<main class="container">
<h1>Bubbling!</h1>
<button id="b1">First!</button>
<button id="b2">Second!</button>
</main>
const main = document.querySelector("main");
const btn1 = document.querySelector("#b1");
const btn2 = document.querySelector("#b2");
function handle(e) {
console.log(`${e.type} target: ${e.target.tagName}, current: ${e.currentTarget.tagName}`);
}
document.body.addEventListener("click", handle);
main.addEventListener("click", handle);
btn1.addEventListener("click", handle);
Píndoles JavaScript
JSDoc
JSDoc permet fer anotacions a JavaScript per a fer comprovacions de tipus i documentar el codi. Si s'utilitza VSCode podem activar aquesta comprovació afegint un arxiu jsconfig.json
amb les propietats strict
i checkJs
:
{
"compilerOptions": {
"strict": true,
"checkJs": true
}
}
Variables
Podem definir el tipus d'una variable:
/** @type {number} */
let result1;
Si la definició té una assignació, JSDoc infereix el seu tipus i no caldria indicar-lo:
let result2 = 0;
Tot i que JavaScript ho permetria, JSDoc no permet canviar el tipus d'una variable.
Funcions
Podem documentar la funció, els seus paràmetres i el valor retornat.
/**
* Add two numbers
* @param {number} number1
* @param {number} number2
* @returns {number}
*/
function add(number1, number2) {
return number1 + number2;
}
JSDoc mostrarà un problema si intentem utilitzar aquesta funció amb un paràmetre que no sigui de tipus number, o bé si intentem assignar el resultat a una variable que no sigui number.
Tipus
Podem indicar els tipus entre dues claus.
- Els primitius:
boolean
,string
,number
. - Els arrays, amb les claus quadrades, p. ex:
string[]
. - Els objectes es poden definir amb
@typedef
.
/**
* User object, surname is optional.
* @typedef {Object} User
* @property {number} id
* @property {string} name
* @property {string} [surname]
* @property {boolean} active
*/
- Les funcions callback es poden definir amb
@callback
, l'equivalent atypedef
per a callbacks:
/**
* A callback function applicable to a number
* @callback ApplyFunc
* @param {number} number
* @returns {number}
*/
O bé directament utilitzant el tipus function
:
/** @type {function(number):string} */
let stringerFn = (num) => (num.toString());
- Podem definir que un paràmetre o variable pot tenir més d'un tipus amb l'OR.
/** @type {number | undefined} */
- Podem fer casting de tipus:
const inputEl = /** @type {HTMLInputElement} */ (document.querySelector('input'));
És important utilitzar parèntesis sobre l'element castejat
- Podem fer tipus condicional:
/**
* @returns {string | number}
*/
function getValue() {
return Math.random() > 0.5? String(17) : 17;
}
const value = getValue();
if (typeof value === 'string') {
// type is string for this block
console.log(`length is ${value.length}`);
}
if (typeof value === 'number') {
// type is number for this block
console.log(`square is ${value*value}`);
}
Desactivació d'errors
Podem desactivar l'error de la línea següent amb:
// @ts-ignore
Podem desactivar tots els errors d'un arxiu amb:
// @ts-nocheck
o activar-los per a un arxiu, si per defecte estan desactivats, amb:
// @ts-check
Precedència d'operadors
Precedence | Operator type | Associativity | Individual operators |
---|---|---|---|
18 | Grouping | n/a | ( … ) |
17 | Member Access | left-to-right | … . … |
Optional chaining | … ?. … |
||
Computed Member Access | n/a | … [ … ] |
|
new (with argument list) |
new … ( … ) |
||
Function Call | … ( … ) |
||
16 | new (without argument list) |
n/a | new … |
15 | Postfix Increment | n/a | … ++ |
Postfix Decrement | … -- |
||
14 | Logical NOT (!) | n/a | ! … |
Bitwise NOT (~) | ~ … |
||
Unary plus (+) | + … |
||
Unary negation (-) | - … |
||
Prefix Increment | ++ … |
||
Prefix Decrement | -- … |
||
typeof |
typeof … |
||
void |
void … |
||
delete |
delete … |
||
await |
await … |
||
13 | Exponentiation (**) | right-to-left | … ** … |
12 | Multiplication (*) | left-to-right | … * … |
Division (/) | … / … |
||
Remainder (%) | … % … |
||
11 | Addition (+) | left-to-right | … + … |
Subtraction (-) | … - … |
||
10 | Bitwise Left Shift (<<) | left-to-right | … << … |
Bitwise Right Shift (>>) | … >> … |
||
Bitwise Unsigned Right Shift (>>>) | … >>> … |
||
9 | Less Than (<) | left-to-right | … < … |
Less Than Or Equal (<=) | … <= … |
||
Greater Than (>) | … > … |
||
Greater Than Or Equal (>=) | … >= … |
||
in |
… in … |
||
instanceof |
… instanceof … |
||
8 | Equality (==) | left-to-right | … == … |
Inequality (!=) | … != … |
||
Strict Equality (===) | … === … |
||
Strict Inequality (!==) | … !== … |
||
7 | Bitwise AND (&) | left-to-right | … & … |
6 | Bitwise XOR (^) | left-to-right | … ^ … |
5 | Bitwise OR (|) | left-to-right | … | … |
4 | Logical AND (&&) | left-to-right | … && … |
3 | Logical OR (||) | left-to-right | … || … |
Nullish coalescing operator (??) | … ?? … |
||
2 | Assignment | right-to-left | … = … |
… += … |
|||
… -= … |
|||
… **= … |
|||
… *= … |
|||
… /= … |
|||
… %= … |
|||
… <<= … |
|||
… >>= … |
|||
… >>>= … |
|||
… &= … |
|||
… ^= … |
|||
… |= … |
|||
… &&= … |
|||
… ||= … |
|||
… ??= … |
|||
Conditional (ternary) operator | right-to-left (Groups on expressions after ? ) |
… ? … : … |
|
Arrow (=>) | right-to-left | … => … |
|
yield |
n/a | yield … |
|
yield* |
yield* … |
||
Spread (...) | ... … |
||
1 | Comma / Sequence | left-to-right | … , … |
TypeScript
- Introducció
- Des de JSDoc
- Transició des de JavaScript
- Build step
Introducció
TypeScript és un superset de JavaScript que afegeix tipat estàtic al llenguatge. Això significa que pots especificar tipus per a variables, paràmetres de funció i retorns de funció, cosa que ajuda a evitar molts errors comuns de programació.
Què afegeix TypeScript:
- Annotations de tipus: TypeScript et permet especificar tipus (per exemple, number, string, boolean, tipus personalitzats) per a variables, paràmetres i valors de retorn, cosa que fa que TypeScript pugui detectar errors en temps de compilació en lloc de temps d’execució.
let age: number = 30; // `age` can only be a number
- Interfaces i alies de tipus: Pots definir l'estructura dels objectes i especificar com han de ser les estructures de dades, fent que el codi sigui més estructurat i previsible.
interface User {
name: string;
age: number;
}
- Enums: TypeScript afegeix enums per definir constants amb nom, fent que el codi sigui més llegible i evitant valors no vàlids.
enum Direction {
North,
South,
East,
West
}
-
Classes i interfícies amb tipat estricte: TypeScript admet classes i interfícies fortament tipades, ajudant a assegurar un ús consistent de classes i objectes en tota l’aplicació.
-
Genèrics: TypeScript afegeix genèrics per escriure funcions i classes reutilitzables i tipus segurs.
function wrapInArray<T>(value: T): T[] {
return [value];
}
Aquests són els avantatges per al desenvolupador:
-
Detecció primerenca d'errors: TypeScript detecta errors durant el desenvolupament, cosa que ajuda a reduir errors i problemes en temps d'execució. Això és especialment útil en projectes més grans.
-
Millora en llegibilitat i mantenibilitat: Amb anotacions de tipus, els desenvolupadors poden entendre amb més facilitat la funció i els valors esperats de variables i funcions, fins i tot quan revisiten el codi després de molt temps.
-
Millor autocompleció i eines: Editors com VSCode poden proporcionar millor autocompleció, suport per a la refactorització i eines de depuració, tot impulsat pel sistema de tipus de TypeScript.
-
Escalabilitat: L’estructura i la seguretat de tipus de TypeScript el fan ideal per a projectes més grans i equips, on el codi ha de ser fàcilment llegible i mantenible per múltiples desenvolupadors.
-
Compatibilitat amb JavaScript: TypeScript es compila en JavaScript pur, així que es pot utilitzar en qualsevol entorn on s'executi JavaScript, incloent navegadors web i Node.js.
Aquests serien alguns inconvenients:
-
Corba d'aprenentatge: TypeScript introdueix sintaxi i conceptes addicionals, que poden requerir temps perquè els desenvolupadors de JavaScript els aprenguin i s'hi adaptin.
-
Pas Addicional de compilació: TypeScript necessita compilar-se a JavaScript, cosa que afegeix un pas de compilació que pot alentir el desenvolupament en alguns fluxos de treball.
-
Sintaxi més estricta: La rigorositat de TypeScript pot semblar pesada per a prototips ràpids o petits scripts on els beneficis del tipat rigorós poden no ser tan evidents.
-
Possibilitat de sobrecàrrega: En projectes petits o tasques de desenvolupament ràpid, l'estructura i definicions de tipus de TypeScript poden de vegades semblar una feina extra sense grans beneficis.
Des de JSDoc
TypeScript i JSDoc milloren ambdós JavaScript amb seguretat de tipus, però difereixen en l'enfocament i les capacitats. TypeScript ofereix un sistema de tipus integrat, que permet la detecció d'errors anticipada en temps de compilació, un excel·lent suport d'eines i escalabilitat per a projectes grans, tot i que requereix un pas de compilació i té una corba d'aprenentatge més pronunciada. En canvi, JSDoc permet anotacions de tipus directament a JavaScript sense compilació, fent-lo flexible i més senzill d'implementar per a projectes petits o migracions graduals, però proporciona una comprovació de tipus limitada i manca les característiques completes de TypeScript, cosa que el fa menys adequat per a codi més gran.
Quan tenim un codi amb JSDoc que volem canviar a TypeScript, començar per reanomenar tots els arxius de .js
a .ts
i treure totes les anotacions de JSDoc. Compte, els imports han d'utilitzar el nom de l'arxiu .js
.
Transició des de JavaScript
Tipus de variables
Afegeix tipus utilitzant : type
després del nom de variable:
let message: string = "Hello, world!";
let count: number = 10;
Tenim dos tipus especials: unknown
i any
.
El tipus any
permet ignorar el sistema de comprovació de tipus, ja que pots fer qualsevol operació sense que es mostrin errors. S'hauria d'evitar sempre que es pugui. Exemple:
let value: any = "Hello";
value = 42; // No error
value.toUpperCase(); // No error, even if value is not a string at runtime
El tipus unknown
és una alternativa més segura a any
. Pot contenir qualsevol tipus, però obliga a fer comprovació de tipus o assercions abans d'utilitzar-ho.
let value: unknown = "Hello";
if (typeof value === "string") {
console.log(value.toUpperCase()); // Safe to call string methods here
}
TypeScript intentarà inferir els tipus segons l'ús que es faci de les variables i les funcions. El consell és treballar amb el compilador en mode estricte (veure la configuració tsconfig.json suggerida més endavant), perquè així es queixarà si hi ha algun tipus indefinit. Alguns casos d'inferència de tipus:
let name = "Alice"; // TypeScript infers `name` as `string`
function add(a: number, b: number) {
return a + b; // TypeScript infers the return type as `number`
}
const numbers = [1, 2, 3];
numbers.forEach(num => console.log(num * 2)); // `num` is inferred as `number`
Aquests són casos on el compilador no pot inferir el tipus:
function greet(name) {
return `Hello, ${name}!`; // 'name' has an implicit 'any' type, but 'strict' mode warns
}
try {
// Some code that might throw an error
} catch (error) {
console.log(error.message); // Error: Object is of type 'unknown'
}
const person = { name: "Alice", age: 25 };
const key = "name" as string;
console.log(person[key]); // Error: expression of type 'string' can't be used to index type
A l'últim cas, la solució té a veure amb utilitzar keyof
:
type Person = { name: string; age: number };
const person: Person = { name: "Alice", age: 25 };
const key: keyof Person = "name"; // Allowed, because `key` is of type `"name" | "age"`
Funcions
Afegeix : type
darrere dels parèntesis de la funció:
function greet(name: string): string {
return `Hello, ${name}`;
}
function logMessage(message: string): void {
console.log(message);
}
Arrays i objectes
Per a arrays, especifica el tipus seguit de []
:
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];
Per a objectes, defineix l'estructura de cada propietat:
let person: { name: string; age: number } = {
name: "Alice",
age: 25
};
Tuples
Una tupla de TypeScript és un array de mida fixa on cada element pot tenir un tipus diferent:
const t1: [number, string] = [1, "Alice"];
const t2: [number, string] = [...t1]; // spreading a tuple
t2[0] = 2; t2[1] = 'Bob'; // mutating a tuple
const t3: unknown = [3, "Eve"]; // tuple without type
const t4 = t3 as [number, string]; // as a tuple
const t5 = t3 as (number | string)[]; // as an array
Interfícies i tipus
Una interfície defineix la forma d'un objecte reusable, de forma molt similar als tipus:
interface PersonI {
name: string;
age: number;
}
type PersonT = {
name: string;
age: number;
};
let person1: PersonI = { name: "Alice", age: 25 };
let person2: PersonT = { name: "Bob", age: 27 };
Les interfícies i els tipus es poden estendre:
// interfaces
interface Animal {
species: string;
}
interface Dog extends Animal {
breed: string;
}
// types
type Animal = {
species: string;
};
type Dog = Animal & {
breed: string;
};
Els tipus permeten definir primitius, unions i interseccions:
type ID = string | number; // Union type
type Coordinates = { x: number; y: number } & { z: number }; // Intersection
Les interfícies permeten la seva implementació per una classe:
interface Person {
name: string;
age: number;
greet(): void;
}
class User implements Person {
constructor(public name: string, public age: number) {}
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
Tipus Union
Les tipus Union permeten una variable acceptar diversos tipus:
function display(value: string | number): void {
console.log(value);
}
Propietats i paràmetres opcionals
Les propietats i paràmetres opcionals s'indiquen amb ?
després del nom:
type User = {
name: string;
age?: number; // age is optional
};
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}`;
}
El tipus de greeting
serà string | undefined
.
Tipus de funcions
Indica en el tipus d'un callback els tipus de l'entrada i la sortida:
function process(data: string, callback: (input: string) => void): void {
callback(data);
}
function processAndReturn(data: number, callback: (input: number) => number): number {
return callback(data);
}
Pots utilitzar tipus:
type Transformer = (input: string) => string;
function transformData(data: string, transformer: Transformer): string {
return transformer(data);
}
Per a utilitzar genèrics:
function mapArray<T, U>(array: T[], callback: (item: T) => U): U[] {
return array.map(callback);
}
const words = ["hello", "world", "typescript"];
const wordLengths = mapArray(words, (item) => item.length);
console.log(wordLengths); // Output: [5, 5, 9]
Comprovacions de tipus
Per assegurar-te que una variable tingui el tipus esperat, pots utilitzar diferents tipus de type guards en TypeScript. A continuació, s'expliquen les tècniques més comunes.
Comprovació per veritat (truthy)
Quan es comprova si una variable no és nul·la ni undefined, pots refinar el tipus simplement verificant si és "truthy":
let str: string | null;
// ...
if (str) { // str no és null ni undefined aquí
console.log(str.length);
}
Tot i que pots utilitzar l'operador !
per indicar que una variable no és nul·la ni undefined
, això pot ser arriscat i cal evitar-ho sempre que sigui possible:
let value: string | null = getValueFromSomewhere();
console.log(value!.length); // Es tracta com no nul·la, però podria fallar si no ho és.
Explícit amb as
Pots informar el compilador d'un tipus específic utilitzant el type assertion as
. Això és útil, però cal fer-ho amb precaució, ja que el compilador confia completament en tu:
let value: any = "This is a string";
let length: number = (value as string).length; // Narrowing explícit
Amb typeof (només primitius)
L'operador typeof
permet verificar el tipus d'una variable per assegurar que és un dels tipus primitius com string
, number
, boolean
, etc.:
let str: unknown;
// ...
if (typeof str === 'string') { // str és un string aquí
console.log(str.length);
}
Amb instanceof (només classes)
L'operador instanceof
s'utilitza per verificar si un objecte és una instància d'una classe concreta:
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
let pet: Dog | Cat;
// ...
if (pet instanceof Dog) {
pet.bark();
} else {
pet.meow();
}
Amb in
El in
permet comprovar si una propietat existeix en un objecte. Això és útil quan treballes amb tipus d'objecte que tenen propietats exclusives:
interface Car {
drive: () => void;
}
interface Boat {
sail: () => void;
}
let vehicle: Car | Boat;
// ...
if ("drive" in vehicle) {
vehicle.drive();
} else {
vehicle.sail();
}
Amb tipus literals
Pots refinar literalment el tipus de les variables amb operadors d'igualtat (===
o !==
) per a unions de literals:
type Shape = "circle" | "square";
function drawShape(shape: Shape) {
if (shape === "circle") {
console.log("Dibuixant un cercle");
} else {
console.log("Dibuixant un quadrat");
}
}
Amb unions discriminades
Quan treballes amb unions de tipus d'objecte que tenen una propietat comuna (el discriminant), pots utilitzar aquesta propietat per refinar el tipus:
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Circle | Rectangle;
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
} else {
return shape.width * shape.height;
}
}
Amb funcions personalitzades
Pots crear els teus propis type guards amb una funció que retorni un tipus de la forma variable is Type
:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
function move(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim();
} else {
animal.fly();
}
}
Control de flux automàtic
TypeScript també pot refinar tipus basant-se en el control de flux sense necessitat de guards explícits. Per exemple, en verificar valors null
o undefined
:
function processValue(value: string | null | undefined) {
if (!value) {
console.log("Valor no proporcionat");
} else {
console.log("Longitud del valor:", value.length);
}
}
Build step
Per a gestionar un projecte amb TypeScript des de zero, cal preparar un package.json
mitjançant npm, instal.lar TypeScript per a desenvolupament i generar la seva configuració. Aquests serien els passos habituals:
$ npm init -y # creates an empty package.json
$ npm install typescript --save-dev # installs typescript package for development
$ npx tsc --init # inits the tsconfig.json file with compiler options
Alternativament, podem editar directament dos arxius de text amb els paràmetres necessaris:
package.json
{
"name": "your-project",
"version": "0.1.0",
"scripts": {
"compile": "tsc",
},
"devDependencies": {
"typescript": "^5.5.4"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es6",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"sourceMap": true
},
"include": [
"src/**/*",
],
"exclude": [
"node_modules"
]
}
Això vol dir que els arxius TypeScript els trobarà a src
. La carpeta node_modules
cal ignorar-la, ja que inclou tots els packages instal.lats de les dependències.
Aquesta configuració també indica que utilitzarà el mode estricte (strict: true
). És una pràctica desitjada, ja que implica escriure codi amb bones pràctiques. Principalment, obliga a fer anotacions de tipus per a les variables i comprovacions de null per als objectes. I no infereix mai que el tipus de la variable és any
si no ha trobat una anotació.
Per a instal.lar les dependències (en aquest cas, typescript) caldrà fer:
$ npm install
Llavors podem compilar a JavaScript així:
$ npm run compile
I tots els arxius js es trobaran a dist
.
Resumint, aquesta seria l'estructura de carpetes:
/your-project
├── /dist // compiled js files
├── /node_modules // packages installed by npm
├── /src // ts files
├── package.json // npm config file
├── tsconfig.json // compiler configuration
Persistència
Model Relacional
- Model relacional
- Model Entitat-Relació
- Claus primàries (PK)
- Claus externes (FK)
- Formes normals
- Bones pràctiques
- Referències
Model relacional
El model relacional permet a un dissenyador de bases de dades crear una representació lògica i consistent de la informació:
- La informació s'estructura mitjançant taules.
- Cada taula es modela amb diversos atributs.
- Les taules contenen files, que tenen valors per cadascun dels atributs.
- Un possible valor d'un atribut és NULL, el no-valor.
- Les files no poden repetir-se.
- Els atributs que identifiquen una fila conformen la clau primària.
- Les taules es relacionen utilitzant claus externes, que referencien atributs d'altres taules. Es diu que la taula amb la clau externa depèn de l'altra, i se solen anomenar taules filla i pare.
La consistència del model s'aconsegueix utilitzant restriccions (constraints), una forma de restringir el domini d'un atribut o implementar regles de negoci.
Hi ha dos tipus d'integritat al model relacional:
- La integritat de l'entitat: cada fila d'una taula té una clau primària única i no nul·la que l'identifica, o sigui, cada fila representa una única instància d'un tipus d'entitat modelada per la taula.
- La integritat referencial: si el valor d'un atribut referencia el valor d'una altra taula, llavors el valor referenciat ha d'existir.
Les principals restriccions del model relacional són:
- La clau primària (PK): un conjunt d'atributs que identifiquen de forma única una fila. No permeten repeticions.
- La clau externa (FK): un conjunt d'atributs que referencien la clau primària d'una altra taula. No permeten referències a files no existents.
- Els índexs únics: indiquen que un índex no permet que hi hagi elements repetits.
- Les comprovacions (checks): permeten afegir regles per als atributs d'una taula que imposen regles sobre les files.
Model Entitat-Relació
El model entitat relació ens permet modelar el món real utilitzant dos conceptes: les entitats i les relacions:
- Entitats, una cosa que existeix al món real i es pot identificar i distingir de la resta. Són instàncies d'un tipus d'entitat o categoria, és a dir, un valor concret. Tenen atributs que les identifiquen i les descriuen. Són substantius.
- Relacions, que expliquen com es relacionen les entitats. Poden ser verbs (o participis), i representen accions o processos entre entitats. Poden tenir atributs per a afegir informació addicional.
Segons la forma d'identificar una entitat, tenim dos tipus:
- Fortes: no depenen de cap altra, i tenen el seu identificador únic.
- Febles: depenen d'una entitat forta per poder ser identificades. Per tant, el seu identificador inclou el de l'entitat forta i un o més atributs addicionals.
Cardinalitat
Els tres tipus de relacions binàries, segons la cardinalitat, són:
- one-to-one: 1⇔1
- one-to-many: 1⇔N
- many-to-many: M⇔N
I les ternàries:
- one-to-one-to-one: 1⇔1⇔1
- one-to-one-to-many: 1⇔1⇔N
- one-to-many-to-many: 1⇔M⇔N
- many-to-many-to-many: M⇔N⇔P
Generalització i agregació
Algunes entitats poden relacionar-se com a una generalització (is-a):
- Supertipus: un tipus genèric (pare) que té una relació amb un o més subtipus (fills).
- Subtipus: un subgrup d'entitats que comparteixen atributs comuns o relacions diferents d'altres subgrups.
Les PK dels supertipus i els subtipus són les mateixes.
Aquesta relació s'utilitza quan alguns atributs només apliquen a certs subtipus, o bé només existeix la relació per a cert subtipus o supertipus.
En canvi, l'agregació és una relació entre un supertipus i un o més subtipus del tipus part-of. A diferència de la generalització, no hi ha atributs heretats.
Transformació a SQL
Abans d'explicar el procés de transformació a partir del model, Cal explicar el concepte de taula associativa. Les taules associatives són una construcció que permet associar dues o més entitats. Per exemple, resol relacions many-to-many creant dos o més relacions one-to-many.
Per a convertir un diagrama ER en taules podem seguir la següent estratègia:
- Per a transformar les entitats:
- Identificar la clau primària de cada entitat.
- Crear una taula per a cada entitat.
- Si un atribut és una FK, crear la restricció corresponent.
- Per a transformar les relacions binàries:
- Identificar les entitats que participen i la seva cardinalitat.
- Si la relació és many-to-many o té atributs, cal crear una taula associativa.
- En cas contrari no cal crear cap taula, només afegir un FK per cada relació.
- Per a transformar les relacions ternàries, crear una taula associativa i:
- 1⇔1⇔1: una PK amb una parella i dos restriccions unique amb les altres dues parelles.
- 1⇔1⇔N: una PK que inclou l'entitat N i una restricció unique incloent l'altra parella amb N.
- 1⇔M⇔N: una PK amb les entitats M i N.
- M⇔N⇔P: una PK amb les tres entitats.
Claus primàries (PK)
Les files d'una taula tenen atributs.
Una superclau és un conjunt d'atributs que identifica de forma única la fila d'una columna. O sigui, no hi ha més d'una fila amb aquest conjunt d’atributs.
Una superclau no és necessàriament un conjunt mínim. Per exemple, la superclau trivial és la de tots els atributs d'una fila. Si anem traient atributs a la superclau fins que no sigui possible treure’n més, llavors tenim un conjunt mínim, o clau candidata, o simplement clau. Si la clau candidata té més d'un atribut es diu que és composta.
Els atributs d'una clau candidata són els atributs principals. Un atribut que no es troba a cap clau candidata és un atribut no principal.
A una taula pot haver-hi més d'una clau candidata. La clau primària és la clau candidata que s'escull formalment al model relacional per a una certa taula. La resta de claus candidates es diuen claus alternatives.
Una clau pot utilitzar atributs existents al món real, i llavors es diu clau natural. Quan només s'utilitza un atribut com a clau, però no té correspondència fora del model relacional, li diem clau substituta. Habitualment són generades automàticament pel SGBD com seqüències numèriques.
Les claus substitutes tenen pros i contres respecte de les naturals:
- Les naturals poden utilitzar-se per cerques, i no requereixen espai addicional de disc. Però si canvien les especificacions, afecten el disseny. També són més complicades i lentes si tenen més d'un atribut.
- Les substitutes resolen els problemes de les naturals, però tenen els problemes que resolen les naturals. A més, trenquen la 3NF, ja que no tenen cap relació amb la fila, i s'implementen de forma diferent segons el SGBD.
Claus externes (FK)
Una clau externa és un conjunt d'atributs d'una taula que fan referència a la clau primària d'una altra taula. La primera es diu taula filla, i la segona, taula pare.
A un SGBD relacional s'espera que hi hagi integritat referencial: si un atribut o atributs es declaren com a clau externa, només poden contenir NULL o bé referir-se a valors existents de la clau primària de la taula pare.
Quan una fila s'actualitza o s'esborra, el SGDB ha de continuar garantint la integritat referencial. Les accions referencials que es poden definir a un fill són:
- CASCADE: el canvi es transmet des del pare al fill.
- RESTRICT o NO ACTION: no permet el canvi en el pare. Opció per defecte si s'omet, habitualment.
- SET NULL: els valors dels atributs que fan la referència es canvien a NULL.
- SET DEFAULT: els valors dels atributs que fan la referència es canvien al valor per defecte.
Formes normals
La normalització s'aplica al disseny relacional per a poder evitar anomalies quan s'insereix, s'esborra o actualitza una fila.
Les formes normals es comproven de forma incremental: 2NF requereix 1NF, 3NF requereix 2NF. Hi ha més formes normals, però les tres primeres ja permeten evitar els problemes habituals associats a un mal disseny.
1NF
Per complir 1NF, cada atribut d'una taula ha de tenir un sol valor (atòmic). A més, no pot haver grups repetits d'atributs, que són atributs anomenats amb un sufix numèric i amb la mateix funció.
StudentID | StudentName | Courses | Instructors | InstructorOffices |
---|---|---|---|---|
1 | Alice | CS101, CS102 | Dr. Smith, Dr. Lee | Room 101, Room 102 |
2 | Bob | CS101 | Dr. Smith | Room 101 |
Solució: crear una taula amb el conjunt de valors com files.
StudentID | StudentName | Course | Instructor | InstructorOffice |
---|---|---|---|---|
1 | Alice | CS101 | Dr. Smith | Room 101 |
1 | Alice | CS102 | Dr. Lee | Room 102 |
2 | Bob | CS101 | Dr. Smith | Room 101 |
2NF
Introduïm el concepte de dependència funcional. Un atribut B és dependent (funcionalment) d'un altre A si a partir d'A obtenim un sol B. A és el determinant i B el depenent, i s'escriu: A ⇨ B.
Això és el que passa habitualment entre una clau i un atribut no principal: l'atribut no principal està determinat per la clau.
La 2NF es dirigeix a claus que tenen més d'un atribut. Per complir-la, cal complir 1NF i, a més, que cada atribut no principal (que no estigui a la clau candidata) depengui funcionalment de tota la clau, no només d'una part.
Solució: si un atribut no principal depèn d'una part, cal moure'l a una taula nova on aparegui només aquesta part.
- student-course table:
StudentID | CourseID |
---|---|
1 | CS101 |
1 | CS102 |
2 | CS101 |
- course table:
CourseID | CourseName | InstructorName | InstructorOffice |
---|---|---|---|
CS101 | Intro to CS | Dr. Smith | Room 101 |
CS102 | Data Structures | Dr. Lee | Room 102 |
- student table:
StudentID | StudentName |
---|---|
1 | Alice |
1 | Bob |
3NF
Introduïm el concepte de dependència transitiva. Si C depèn de B i B de A, llavors C depèn (transitivament) de A. O sigui: si B ⇨ C i A ⇨ B, llavors A ⇨ C.
Per complir 3NF, cal complir 2NF i, a més, que no hi hagi cap atribut no principal que depengui transitivament de la clau primària.
Solució: crear dues taules sense dependències transitives. A cada una hi ha la dependència B de A i a l'altra C de B, respectivament.
- course table:
CourseID | CourseName | InstructorID |
---|---|---|
CS101 | Intro to CS | 1 |
CS102 | Data Structures | 2 |
- instructor table:
InstructorID | InstructorName | InstructorOffice |
---|---|---|
1 | Dr. Smith | Room 101 |
2 | Dr. Lee | Room 102 |
Bones pràctiques
Sobre com anomenar:
- Utilitzar minúscules i subratllats per a separar paraules.
- Hi ha dues pràctiques per a anomenar taules: utilitzar singular o plural. Preferiblement, noms col·lectius o plurals.
- Els atributs sempre són singulars.
- No passa res si dues taules tenen atributs amb el mateix nom.
- Identificar els atributs que contenen les PK i FK i utilitzar un sufix. Per exemple, nom de la taula més _id.
Sobre integritat:
- Utilitzar sempre restriccions en lloc de fer comprovacions al codi.
- Preferir entitats fortes a febles. Simplifiquen el disseny i generen consultes més òptimes.
- En general, no definir atributs que siguin derivats d'altres.
- Evitar sempre que sigui possible els atributs nullables. Estratègies:
- Utilitzar una relació one-to-one opcional.
- Utilitzar el valor per defecte a la definició de l'atribut.
- Definir com a no nullable aquells atributs que no puguin ser NULL.
- Cal pensar que pot haver-hi múltiples connexions concurrents incidint sobre les mateixes files. Per tant, cal utilitzar transaccions sempre que calgui que un conjunt de comandes es facin totes o cap.
Sobre claus primàries i externes:
- Si la PK no és substituta, millor que sigui immutable o molt estable.
- És millor no implicar molts atributs a la PK. Fa perdre estabilitat. Potser hi ha una clau amb menys camps, o és millor utilitzar una clau substituta.
- Si la PK pot canviar, cal utilitzar ON UPDATE CASCADE al FK per rebre els canvis. Això no cal amb claus substitutes, ja que no canvien.
- Compte amb ON DELETE CASCADE. És preferible esborrar explícitament les files, i que si hi ha un problema d'integritat la restricció no permeti l'operació. Podria tenir sentit utilitzar-ho amb entitats febles.
- ON UPDATE SET NULL/DEFAULT tenen poc sentit, només en tenen pel DELETE, i només si la taula filla és una entitat forta.
- És un problema tenir una FK amb diversos atributs on alguns poden ser NULL. En general, una FK és NULL si qualsevol part ho és.
Sobre optimització, modelar pensant en les consultes que realitzarà l'aplicació sobre la base de dades. Això pot tenir una incidència sobre l'esquema i sobre els índexs, per exemple:
- Cercar sempre sobre camps que estan indexats.
- Afegir índexs sobre els atributs dels joins. No cal per a les PK, s'indexen per defecte.
Referències
- Database normalization
- Database guide
- Surrogate key vs Natural key
- SQL style guide
- The realities of the relational database model
- M2 - Bases de dades
- DB Design with UML and SQL
- When (and How) to Use Surrogate Keys
- Database Modeling and Design
Model NoSQL
Model NoSQL
El principal problema d'un model relacional és la distribució d'una BBDD en diferents servidors per raó de mida i rendiment (clústers), i que pugui ser accedida com una sola.
El funcionament en clústers utilitza dues idees:
- La replicació: repliquem les dades en master-slave o peer-to-peer.
- El sharding: situem diferents parts de les dades en diferents servidors.
Les BBDD relacionals no estan dissenyades per a funcionar eficientment en clústers, aquesta és la raó principal per a migrar a NoSQL. La segona és que es veu com una una forma de millorar la productivitat en el desenvolupament d'aplicacions, ja que la interacció amb les dades és més còmoda.
Un model agregat és una col·lecció de dades amb les quals interactuem com a una unitat. Els models agregats permeten treballar més fàcilment amb clústers, agafant l'agregació com a unitat de replicació i sharding. A més, l'agregació també facilita la feina dels desenvolupadors, perquè la manipulació de dades es produeix molt sovint a nivell de agregat.
Els tipus principals de BBDD NoSQL són els models agregats (key-value, document i column) i els de graf.
Key-Value
És la forma d'agregació. Contenen col·leccions de parelles clau-valor, com els diccionaris o les taules hash. La clau és l'identificador i les dades poden ser un objecte JSON, un blob binari o un altre tipus.
Key | Value |
---|---|
"user:1" | { "name": "Alice", "age": 25 } |
"user:2" | { "name": "Bob", "age": 30 } |
S'utilitzen per a fer caching, gestió de sessions o mètriques senzilles. Són molt fàcils d'implementar, eficients i escalables. Però no permeten fer queries gaire complexes.
Exemples populars: Redis, Amazon DynamoDB (mode key-value).
Document
Les dades s'organitzen el documents de format JSON, BSON o XML. Tenen una estructura jeràrquica, que els atorga flexibilitat. Un document és una unitat atòmica que pot ser consultada, modificada o esborrada. És una entitat que freqüentment té les dades relacionades al mateix document.
Per exemple, aquest seria un usuari amb les ordres associades. No cal predefinir les seves columnes, ni fer queries per trobar les ordres de l'usuari:
{
"_id": "user:1",
"name": "Alice",
"age": 25,
"orders": [
{ "orderId": "A100", "amount": 50.5 },
{ "orderId": "A101", "amount": 25.0 }
]
}
Les queries permeten fer filtratge, agregació o indexació. Per exemple:
- Trobar usuaris més grans de 20:
{ "age": { "$gt": 20 } }
- Obtenir una certa ordre:
{ "orders.orderId": "A100" }
Aquest tipus de model permet esquemes flexibles, dades jeràrquiques i també camps indexables per a fer queries més ràpides. Són molt flexibles per no tenir esquema, però l'organització pot portar duplicacions o la impossibilitat de fer relacions (les foreign keys relacionals).
Es poden utilitzar a gestors de continguts, llocs d'e-commerce o per fer event logging.
Exemples populars: MongoDB, Couchbase, Amazon DocumentDB.
Column
Aquest model organitza les dades en columnes i files, però al contrari que el model relacional, les columnes s'agrupen en famílies. Cada familia conté columnes, el que permet optimitzar els patrons d'accés. I cada fila té una clau única.
Exemple:
Row Key | PersonalInfo | OrderInfo |
---|---|---|
1 | {name: "Alice", age: 25} | {order1: 50.5} |
2 | {name: "Bob", age: 30} | {order1: 75.0, order2: 25.0} |
S'utilitzen per a emmagatzemar esdeveniments amb timestamps o datasets molt grans amb dades disperses. Permeten operacions d'escriptura intensives, però el modelatge i les queries són difícils d'implementar.
Exemples populars: Apache Cassandra, HBase.
Comparativa
Funcionalitat | Model Key-Value | Model Document | Model Column-Family |
---|---|---|---|
Estructura | Parelles Key-Value | Documents JSON/BSON/XML | Files agrupades en families |
Consultes | Només per clau | Per camp, consultes niades | Per clau de fila, rang de columna |
Casos d'ús | Caching, cerques ràpides | Esquemes flexibles, dades jeràrquiques | Anàlisi de dades, series temporals |
Flexibilitat d'esquema | Senzilla | Alta | Moderada |
Referències
Projectes
Workflow Git
Aquesta entrada descriu un flux de treball simplificat per treballar en equips de desenvolupament petits, des de la línia de comanda i sense crear branques.
Conceptes
En local
git és una eina que permet treballar amb repositoris de codi locals i remots.
Els canvis sobre els arxius d’un repositori s’agrupen en commits. Un commit és l’acte d’emmagatzemar un conjunt de canvis al repositori.
En l’àmbit local, tenim tres espais:
- El working directory és el lloc on tens el teu codi. A l’arrel del teu working directory tindràs sempre una carpeta anomenada .git on es guarden els altres dos espais.
- El staging area és una capsa on pots ficar i treure arxius. Un commit estarà format per tots els arxius ficats en aquesta àrea, i s’identifica amb un hash o resum. Quan es fa el commit, es buida.
- El repositori és el lloc on s’emmagatzemen els commits d’arxius provenents del stagging area. Podem revisar i recuperar qualsevol arxiu de qualsevol commit del passat. El commit actual d’un repositori es diu HEAD.
Un repositori pot tenir branques (branches). Les branques permeten divergir de la línia principal de desenvolupament i fer feina sense afectar-la. En aquest document no les utilitzarem, per tal de tenir un flux de treball més senzill. Però cal saber que master és el nom de la branca que git crea per defecte quan es crea un repositori.
En remot
Opcionalment, podem tenir repositoris remots, i comunicar-nos per pujar o baixar coses. Un repositori remot és com un de local, però no té working directory. Se’n diu “bare”.
Ens interessa tenir-ne de remots per poder tenir un lloc on compartim el codi amb la resta de membres del grup. El flux de treball serà treballar en local i compartir en remot la feina, un cop la tenim enllestida.
A un repositori local podem emparellar un de remot. El nom que git dona al principal repositori remot és origin. Un cop els hem emparellat, el codi NO se sincronitza automàticament. Tenim disponibles una sèrie d’operacions:
- fetch: guarda en local els canvis remots (sense integrar-los).
- merge: barreja els canvis remots que tenim en local amb els locals.
- pull: és el mateix que fer un fetch i després un merge.
- push: puja tots els canvis locals al repositori remot.
Escenari base
Reproduirem l’escenari base, amb dos usuaris i un repositori remot compartit. Els dos usuaris fan canvis en local i els sincronitzen amb el repositori remot.
Eina git
Instal·la la teva eina git de línia de comanda al teu sistema operatiu.
Intenta executar-la: “git –version”:
$ git --version
Crear el repositori
Primer, has de crear un repositori buit a github o a gitlab.
Quan l’hagis creat, pots obtenir un URL del tipus:
https://github.com/usuari/repositori.git
o bé https://gitlab.com/usuari/repositori.git
.
Configuració
Les següents tres comandes són interessants per treballar: les dues primeres, calen per indicar el teu usuari i correu que es guarda a l’activitat del repositori. El tercer serveix per guardar les credencials el primer cop que s’introdueixen. Compte: es guarden en text pla a $HOME/.git-credentials
.
$ git config --global user.email "elteu@correu.com"
$ git config --global user.name "elteunom"
$ git config --global credential.helper store
El flag --global indica que els canvis apliquen a tots els repositoris. Si no s'indica, només aplica al repositori en què ens trobem.
També es pot configurar credential.helper per utilitzar una cache (900 segons per defecte):
$ git config --global credential.helper cache
Si es volen ignorar els canvis fets al mode dels arxius, es pot fer:
$ git config --global core.filemode false
La comanda per esborrar una entrada és:
$ git config --global --unset <key>
Clonar el repositori
A partir d’ara es parla de github, però les comandes són exactament les mateixes canviant l’URL pel de gitlab.
Clonarem el repositori buit que hem creat a github:
1$ git clone https://github.com/usuari/repositori.git
Això crea una carpeta “repositori” amb el working directori i la carpeta .git a dins.
Per mostrar l’estat:
1$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
També pots mirar l’aparellament amb el repositori remot:
1$ git remote -v
origin https://github.com/usuari/repositori.git (fetch)
origin https://github.com/usuari/repositori.git (push)
Pots veure les branques locals i remotes així:
1$ git branch
* master
1$ git branch -r
origin/HEAD -> origin/master
origin/master
Afegir contingut
Per afegir contingut, cal preparar el commit. Primer, crea o copia al working directory tot el contingut que vulguis.
Imaginem que afegim un arxiu així:
$ echo "Hola, món!" > arxiu.txt
Si mostres l’estat:
1$ git status
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
arxiu.txt
nothing added to commit but untracked files present (use "git add" to track)
Els missatges expliquen que tenim un arxiu fora del control del repositori (untracked). Per afegir-lo:
1$ git add arxiu.txt
1$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: arxiu.txt
Ara explica que tenim un nou arxiu (new file) al staging area. Per afegir-lo al commit:
1$ git commit -m "primer commit"
[master (root-commit) 17466a8] primer commit
1 file changed, 1 insertion(+)
create mode 100644 arxiu.txt
1$ git status
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)
nothing to commit, working tree clean
També pots mirar el log, el lloc on es guarden els canvis del repositori:
1$ git log
commit 17466a86c10203150c8502e3aaedb8066c9d9b67 (HEAD -> master)
Author: elteunom <elteu@correu.com>
Date: Sun Apr 26 19:39:54 2020 +0200
primer commit
També hi ha un format en una línia d’aquesta comanda:
1$ git log --graph --oneline
* 17466a8 (HEAD -> master) primer commit
Està dient que tenim un commit “17466a8” i que és l’actual.
Pujar contingut
Cal fer un push:
1$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 216 bytes | 216.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
* [new branch] master -> master
Nou estat:
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Ara ens diu que estem sincronitzats amb origin/master. Nou log:
1$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master) primer commit
Ara ens apareix origin/master.
Treballar amb un segon usuari
Simularem que tenim un segon usuari amb un altre repositori. Per simplificar, els dos usuaris poden compartir credencials de github. Alternativament (recomanable), crea tants usuaris com calgui, i fes que siguin col.laboradors del projecte. Això es pot fer tant a github com a gitlab:
- github: cal afegir un col·laborador des del projecte > Settings > Manage access > Invite a collaborator.
- gitlab: cal afegir un membre des del projecte > Settings > Members > Invite member. Selecciona “mantainer” com a perfil.
Creem un segon workspace directory. Per distingir els dos, tindrem dos prompts diferents: 1$ i 2$.
2$ git clone https://github.com/usuari/repositori.git
Cloning into 'repositori'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
Ara, modificarem l’arxiu i mirem l’estat:
2$ echo "Com ba tot?" >> arxiu.txt
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Ens diu que hi ha un arxiu modificat (modified), però no està al staging area.
Ens hem equivocat! Volíem escriure “Com va tot?”. Podríem editar l’arxiu un altre cop i esborrar la nova línia, però aprofitarem per recuperar l’arxiu abans de fer la modificació. Com que no hem fet el commit, es pot recuperar així:
2$ git reset --hard
HEAD is now at 17466a8 primer commit
2$ echo "Com va tot?" >> arxiu.txt
Ara, afegim l’arxiu al staging area i fem el commit:
2$ git add arxiu.txt
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: arxiu.txt
2$ git commit -m "afegim pregunta"
[master b475802] afegim pregunta
1 file changed, 1 insertion(+)
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
Ara, afegim els canvis al repositori remot:
2$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 263 bytes | 263.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
17466a8..b475802 master -> master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Ja tenim tots els canvis en remot. Mirem el log:
2$ git log --graph --oneline
* b475802 (HEAD -> master, origin/master, origin/HEAD) afegim pregunta
* 17466a8 primer commit
Com es veu, l’últim commit (b475802: “afegim pregunta”) es mostra com l’actual.
Rebre els canvis al primer usuari
Ara retornem al primer usuari (1$). Si mirem l’estat i el log:
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master) primer commit
Com es pot veure, l’estat diu que està actualitzat amb origin/master (repositori remot), i al log no hi ha el nou commit que s’ha pujat al repositori remot (“primera pregunta”).
Per poder veure’l, cal baixar-se els canvis del remot. Això es pot fer amb un fetch:
1$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/usuari/repositori
17466a8..b475802 master -> origin/master
Si es mira l’estat i el log:
1$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
1$ git log --graph --oneline
* 17466a8 (HEAD -> master) primer commit
El log no ha canviat, perquè fetch no integra els canvis al repositori, però l’estat sí: ara ens diu que estem per darrere d’origin/master, i que hauríem de fer un git pull. Com que un pull és un fetch + merge, farem només el merge.
El merge intentarà barrejar automàticament el contingut remot recuperat i el que tenim al working directory.
1$ git merge
Updating 17466a8..b475802
Fast-forward
arxiu.txt | 1 +
1 file changed, 1 insertion(+)
El merge ha funcionat: ha afegit una nova línia. Com es veu, aquesta operació és immediata: no necessita anar al repositori remot. L’arxiu s’ha actualitzat, i l’estat i el log estan igualats amb els de l’usuari 2:
1$ cat arxiu.txt
Hola, món!
Com va tot?
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* b475802 (HEAD -> master, origin/master) afegim pregunta
* 17466a8 primer commit
Etiquetes
Les etiquetes (o tags) és una forma senzilla d’identificar un cert commit o estat dins del repositori. Es poden posar locals i pujar-les en remot. A github, quan es pugen en remot, s’associen a una release que permet descarregar un arxiu empaquetat. A gitlab també es pot fer, però la secció es diu “tags”.
Per afegir un tag al commit actual i mostrar-lo:
1$ git tag v1.0
1$ git tag
v1.0
Per pujar una etiqueta:
1$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
* [new tag] v1.0 -> v1.0
Si volem veure les etiquetes des de l’altre repositori:
2$ git fetch
From https://github.com/usuari/repositori
* [new tag] v1.0 -> v1.0
2$ git tag
v1.0
Checkout i altres
El checkout ens permet recuperar qualsevol working directori per a un commit. És la veritable raó de ser dels repositoris: poder viatjar en el temps.
Per exemple, podem recuperar un arxiu concret d’un commit. De l’últim ( — significa que no indiquem el commit), o d’un concret:
1$ git checkout -- arxiu.txt
1$ git checkout 17466a8 arxiu.txt
Podem recuperar tot un commit, per exemple, el “primer commit”:
1$ git checkout 17466a8
Note: checking out '17466a8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 17466a8 primer commit
1$ git status
HEAD detached at 17466a8
nothing to commit, working tree clean
1$ cat arxiu.txt
Hola, món!
Com es veu, tornem al contingut de l’arxiu abans del segon commit.
El problema d’aquesta comanda és que estem en estat “detached HEAD”: no tenim commit actual i no podríem treballar amb el staging area.
Sempre podem retornar al master:
1$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
I si volem recuperar cert commit al master, per exemple, el “primer commit”:
1$ git reset --hard 17466a8
HEAD is now at 17466a8 primer commit
També podem fer referència a una etiqueta:
1$ get reset --hard v1.0
Si volem que el reset quedi al repositori remot:
1$ git push
To https://github.com/usuari/repositori.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/usuari/repositori.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Això falla perquè no es pot fer un push d’un commit antic: git sempre comprova que siguin commits més nous. Podem ometre aquesta comprovació amb el paràmetre –force:
1$ git push --force
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
+ b475802...17466a8 master -> master (forced update)
Després de fer això, si anem a l’altre repositori i fem fetch:
2$ git fetch
From https://github.com/usuari/repositori
+ b475802...17466a8 master -> origin/master (forced update)
2$ git log --graph --oneline
* b475802 (HEAD -> master, tag: v1.0) afegim pregunta
* 17466a8 (origin/master) primer commit
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Veiem que el repositori remot està al primer commit, però el local està al segon: ens diu “your branch is ahead”.
Això es pot resoldre canviant al commit remot en local:
2$ git reset --hard origin/master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
2$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master, origin/HEAD) primer commit
Una altra eina molt útil és git diff. Ens permet comparar dos commits locals o remots.Per exemple, per comparar el HEAD amb el tag v1.0 cal utilitzar un paràmetre amb el commit:
$ git diff v1.0
diff --git a/arxiu.txt b/arxiu.txt
index 0027e65..b4b62f7 100644
--- a/arxiu.txt
+++ b/arxiu.txt
@@ -1,2 +1,2 @@
Hola, món!
-Com va tot?
+segona línia 13
En aquest cas, ens diu que el canvi del tag v1.0 al HEAD és que s’ha esborrat una línia (“Com va tot?”) i s’ha afegit una altra (“segona línia 13”).
Si volem comparar dos commits, afegirem dos paràmetres. Per exemple, si hem fet prèviament un git fetch, podem utilitzar aquesta comanda per veure si el master local i el remot estan sincronitzats:
$ git diff master origin/master
Una altra comanda útil és la d’esborrar arxius o carpetes. Les següents comandes permeten esborrar un arxiu o una carpeta. L’opció –cached permet esborrar només del repositori, no del workind directory. L’opció -r permet, per carpetes, fer la feina de forma recursiva.
$ git rm arxiu.txt
$ git rm --cached arxiu1.txt
$ git rm -r carpeta
Un cop fet això, cal afegir-ho a un commit amb git add nomdarxiu
o bé el contingut de la carpeta i totes les subcarpetes amb git add .
.
Gitignore
Alguns tipus de fitxers no haurien de ser part del repositori de codi, i es poden indicar afegint un patró a l’arxiu .gitignore que hi ha a les carpetes. En general, seria millor no afegir certs tipus d’arxius:
- cachés de dependències, com els continguts de
/node_modules
o/packages
- codi compilat, com
.o
,.pyc
, i.class
- carpetes de sortida de compilació, com
/bin
,/out
, o/target
- arxius generats en temps d’execució com
.log
,.lock
, o.tmp
- arxius amagats del sistema, com
.DS_Store
oThumbs.db
- arxius de configuració personal dels IDE, com
.idea/workspace.xml
Escenari amb conflicte
El conflicte més típic entre dos usuaris és que modifiquin la mateixa línia d’un arxiu. Quan els dos vulguin fer un push, el segon no podrà fer-lo amb un missatge d’error.
El primer fa:
1$ echo "segona línia 1" >> arxiu.txt
1$ git add arxiu.txt
1$ git commit -m "segona 1"
[master 8bf099d] segona 1
1 file changed, 1 insertion(+)
1$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 262 bytes | 262.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
17466a8..8bf099d master -> master
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
I el segon:
2$ echo "segona línia 2" >> arxiu.txt
2$ git add arxiu.txt
2$ git commit -m "segona 2"
[master eacb48e] segona 2
1 file changed, 1 insertion(+)
2$ git push
To https://github.com/usuari/repositori.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://github.com/usuari/repositori.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Com es veu, es diu que cal fer primer pull, ja que no pots fer push si no has integrat els canvis remots al teu repositori.
Provem de fer-ho. Primer el fetch:
2$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/usuari/repositori
17466a8..8bf099d master -> origin/master
2$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean
Ens demana el pull, farem el merge (ja hem fet el fetch). És important fer sempre el merge en un repositori on no hi hagi canvis pendents del commit.
2$ git merge
Auto-merging arxiu.txt
CONFLICT (content): Merge conflict in arxiu.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Ja tenim el conflicte a arxiu.txt. Això es tradueix en el fet que git modifica l’arxiu del conflicte per a reflectir les dues versions, afegint tres delimitadors:
- <<<<<< HEAD
- La versió local
- ======
- La versió remota
- >>>>>> nom_de_la_branca
En el nostre cas:
2$ cat arxiu.txt
Hola, món!
<<<<<< HEAD
segona línia 2
======
segona línia 1
>>>>>> refs/remotes/origin/master
En aquest punt, ens podríem fer enrere (no ho farem) fins a l’estat anterior del merge fent git merge --abort
.
Ens diu que teníem “segona línia 2” (HEAD) i que al remot tenim “segona línia 1” (refs/remotes/origin/master). Per resoldre el conflicte manualment, hem d’editar aquest arxiu i decidir què fem, esborrant les línies delimitadores (<,=,>) i tot el que no ens interessi.
En el nostre cas, decidim que ni una línia ni l’altra: “segona línia 12”. Editem l'arxiu:
2$ cat arxiu.txt
Hola, món!
segona línia 12
Després d'editar-lo, cal fer git add
per marcar el conflicte com a resolt i ja podem fer commit i push:
2$ git add arxiu.txt
2$ git commit -m "resolt!"
[master 6fada39] resolt!
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
2$ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 523 bytes | 523.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
8bf099d..6fada39 master -> master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
2$ git log --graph --oneline
* 6fada39 (HEAD -> master, origin/master) resolt!
|\
| * 8bf099d segona 1
* | eacb48e segona 2
|/
* 17466a8 primer commit
Es poden veure els dos commits en paral·lel, i com finalment hi ha un commit (6fada39) que resol el problema.
Ara tornem al repositori 1:
1$ git fetch
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
From https://github.com/usuari/repositori
8bf099d..6fada39 master -> origin/master
1$ git status
On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
1$ git log --graph --oneline
* 8bf099d (HEAD -> master) segona 1
* 17466a8 primer commit
1$ git merge
Updating 8bf099d..6fada39
Fast-forward
arxiu.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* 6fada39 (HEAD -> master, origin/master, origin/HEAD) resolt!
|\
| * 8bf099d segona 1
* | eacb48e segona 2
|/
* 17466a8 primer commit
I ja tenim els dos repositoris sincronitzats després del conflicte.
Flux
Aquest és el flux de treball proposat:
- Obtenir canvis remots, en dos passos:
- Obtenir-los amb git fetch
- Barrejar-los amb git merge
- Si el merge genera conflicte:
- Editar arxius conflictius
- Fer git add de les solucions
- Fer git commit
- Fer canvis en local:
- Modificar els arxius del working directory
- Afegir-los al staging area (git add)
- Fer commit (git commit)
- Pujar canvis locals:
- Fer git push
FAQ
Alternatives per crear un repositori
Suposem que l'usuari myuser ha creat un nou repositori buit a gitlab anomenat myproj.
Crear un nou repositori
$ git clone https://gitlab.com/myuser/myproj.git
$ cd myproj
$ git switch -c main
# afegir arxius i .gitignore i fer commit d'ells
$ git push -u origin main
Pujar una carpeta existent
$ cd myproj
$ git init
$ git symbolic-ref HEAD refs/heads/main # millor main que master
$ git remote add origin https://gitlab.com/myuser/myproj.git
# afegir .gitignore preferit
$ git add .
$ git commit -m "commit inicial"
$ git push -u origin main
Referències
Empresa
- Economia
- Societat
- Sector informàtic
- Proposta de valor
- Estudi de mercat
- Màrqueting
- Estructura legal i pla econòmic
- Pla d'empresa
Economia
Sistema econòmic
El sistema econòmic és l'organització d'una societat per a gestionar els recursos de què disposa. En funció de la regulació del mercat, en tenim dos tipus:
- El capitalisme, que es basa en la propietat privada. La finalitat és satisfer les necessitats humanes amb un mercat desregulat que funciona gràcies a la competència i la cerca del benefici econòmic.
- El socialisme, que es basa en la propietat col·lectiva. La finalitat és aconseguir una societat justa i solidària amb el repartiment de la riquesa mitjançant la planificació i mediació al mercat.
Aspectes | Capitalisme | Socialisme |
---|---|---|
Orígen | Segle XIII | Segle XIX |
Propietat dels medis de producció | Privada | Social |
Mecanisme d'assignació | Mercat | Estat |
Principals factors de producció | Capital | Treball |
Classes socials | Segons el poder econòmic | No hi ha classes |
Llibertat de decisió | Existeix libertat | Libertat limitada |
Treball | Dret | Deure |
Distribució de la riquesa | Sistema meritocràtic | Sistema igualitari |
Defensa d'interessos | Individual | Col·lectiva |
Objectiu | Maximització del benefici econòmic | Maximizació del benestar social |
Marc institucional | Descentralizació | Centralizació |
El sistema més habitual al món és l'economia mixta, que barreja els dos: tenim un sector privat i un de públic que regula i corregeix el primer en la senda del progrés social. Una conseqüència d'aquest model és l'estat del benestar, on l'estat utilitza part del seu pressupost per a assegurar que tots els ciutadans arriben a un mínim de recursos per a viure dignament.
La regulació del mercat per part de l'estat estableix un marc que pretén protegir l'economia de les seves imperfeccions. Per exemple:
- Quin ha de ser el nivell dels salaris per poder viure dignament.
- Com es resol la falta de competència (monopolis).
- Com es proporcionen els serveis essencials.
- Establir mecanismes justos per a la redistribució de la riquesa.
- Evitar l'enginyeria fiscal per a l'evasió i elusió fiscal.
La doctrina política del neoliberalisme és un corrent amb molta força que vol defensar el capitalisme reduint al màxim la intervenció de l'estat. És el causant de la reducció de l'estat del benestar i les privatitzacions de béns públics dels darrers anys al nostre país.
PIB i creixement
El principi del sistema capitalista és que necessitem créixer perquè funcioni l'economia. El concepte s'anomena creixement econòmic. La tendència és de creixement, però el comportament és cíclic, i ho fa en els anomenats cicles econòmics. Cada cicle té una sèrie de fases: recuperació, expansió, auge, recessió i depressió.
L'indicador més utilitzat per a valorar la situació de l'economia és el PIB (producte interior brut): el valor monetari de tots els béns i serveis finals produïts per una regió durant un any. Es diu que si creix l'economia, llavors ho fan els béns i serveis, i tots ens beneficiem.
Els crítics amb la teoria del creixement econòmic basada en el PIB diuen que:
- Créixer no genera necessàriament cohesió social.
- Provoca agressions al medi, moltes irreversibles.
- Esgota els recursos, que no estaran disponibles a les properes generacions.
- Facilita el mode de vida esclau, on serem més feliços quan més treballem, més guanyem i més consumim.
A més, els pilars del sistema econòmic capitalista es basen en principis poc ètics:
- La primacia de la publicitat, que ens obliga a comprar el que no necessitem.
- El crèdit, que ens permet obtenir recursos pel que no necessitem.
- La caducitat dels productes (obsolescència programada).
Els defensors del creixement afirmen que el mateix sistema, amb el mecanisme de la competència, s'encarregarà de resoldre els problemes que genera. Per exemple, amb solucionisme tecnològic.
També hi ha alternatives de responsabilitat social, que es refereixen a creixement sostenible per expressar que és possible créixer però fer-ho en favor el medi i el benestar de la societat. Aquestes plantegen substituir el PIB com a indicador, però encara no hi ha un consens:
- Índex de desenvolupament humà (IDH).
- Felicitat interna bruta (FIB).
- Índex de benestar canadenc (IBC).
- Renda nacional bruta modificada (RNB*).
- Índex de progrés real (IPR).
La teoria del decreixement és un moviment polític, econòmic i social favorable a la disminució regular controlada de la producció econòmica, amb l'objectiu d'establir una nova relació entre l’ésser humà i la natura.
Els principis d'actuació d'aquesta teoria segons Serge Latouche són:
- Reavaluar els valors individualistes i consumistes i substituir-los per ideals de cooperació.
- Reconceptualitzar l'estil de vida actual.
- Reestructurar els sistemes de producció i les relacions socials en funció de la nova escala de valors.
- Relocalitzar: es pretén reduir l'impacte generat pel transport intercontinental de mercaderies i se simplifica la gestió local de la producció.
- Redistribuir la riquesa.
- Reduir el consum, simplificar l'estil de vida dels ciutadans. El decreixement aposta per una tornada al petit i al que és simple, a aquelles eines i tècniques adaptades a les necessitats d'ús, fàcils d'entendre, intercanviables i modificables.
- Reutilitzar i reciclar: allargar el temps de vida dels productes per evitar el malbaratament. Evitar el disseny de productes obsolescents.
Emprenedoria
Autoocupació
L'autoocupació permet a una persona obtenir ingressos mitjançant una activitat que crea per a un mateix, en lloc de treballar per una altra persona o organització (el treball per compte d'altri). Bàsicament tenim dues opcions:
- Ser treballador independent. Associat als conceptes de treballador autònom o freelance.
- Crear una empresa capitalista, és a dir, una organització de dues o més persones que busca un benefici econòmic mitjançant el desenvolupament d'una activitat. Una empresa (societat) pertany als socis capitalistes. Quan hi ha beneficis, es poden reinvertir en l'empresa o bé repartir dividends entre els socis capitalistes.
L'emprenedoria és la iniciativa d'un emprenedor de portar a la pràctica una idea de negoci, és a dir, crear una empresa i dur a terme la producció del bé o prestació de servei. El coneixement, relacions i capacitats de l'equip són essencials a l'hora de definir una bona idea. Es parla d'intraprenedor en referència als emprenedors que exerceixen com a tals en empreses de les quals no són titulars. Finalment, l'empresari (pot ser o no el mateix l'emprenedor) és qui després dirigeix i gestiona l'empresa.
Un emprenedor haurà de prendre decisions, assumir riscos, ser creatiu, constant, tenir confiança en si mateix, sentit pràctic, saber organitzar-se i tenir facilitat per a les relacions personals. Pot tenir diferents motivacions, com econòmiques, socials o tecnològiques.
Visió, missió i valors
Una eina per definir l'estratègia per a un emprenedor és la de la visió, la missió i els valors. Tot parteix de fer explícita la seva visió:
- Què volem ser i on volem arribar?
- Com ens enfrontarem al canvi?
- Al mercat només podem accedir amb un avantatge competitiu. Quin és i com el podrem mantenir?
Aquesta visió ha de ser clara, concisa, memorable i ha de definir un futur desitjable.
Per altra banda, la missió és la raó d'existir de l'organització, i respon a les preguntes: què fem, per a qui, quines necessitats satisfem, i com ens diferenciem.
Finalment, els valors ens indiquen quines són les nostres formes particulars per seguir el camí que ens porta a la visió. Proporcionen un criteri en el moment de prendre decisions incertes, que en alguns casos podrien ser una solució fàcil però que portarien a trair els nostres valors.
Estratègia de negoci
En els negocis, un avantatge competitiu és un atribut que permet a una organització superar els seus competidors.
Una eina per a determinar l'avantatge competitiu d'una empresa és la cadena de valor, que permet examinar i dividir l'empresa en les seves activitats estratègiques més rellevants i entendre els costos, les fonts i la seva diferenciació.
Tenim dos tipus d'activitats a la cadena de valor:
- Les primàries o competències distintives (core business), enfocades a l'elaboració del producte o servei i la seva venda: logística interna i externa, operació (producció), màrqueting i vendes, servei post-venda.
- Les de suport: infraestructura, recursos humans, recerca i desenvolupament (R+D) i compres.
En el context de la globalització, quan una empresa pot desenvolupar activitats en altres llocs geogràfics, se'n parla de cadena global de valor.
Els avantatges competitius d'una activitat econòmica poden classificar-se en tres:
- Lideratge en costos amb qualitats similars. Es pot fer a diferents punts de la cadena de valor.
- Diferenciació d'un producte o servei de certa complexitat segons la combinació de la seva qualitat i les seves característiques.
- Segmentació de mercat segons variables geogràfiques, personals, psicogràfiques o de comportament.
Aquestes són algunes estratègies que poden diferenciar-nos dels nostres competidors:
- Visibilitzar l'equip, destacant l'experiència, fiabilitat i capacitat de resoldre problemes complexos.
- Bona experiència de client amb un servei excel·lent, comunicació clara i freqüent i transparència.
- Amplia distribució i accés als serveis, associant-se amb proveïdors o utilitzant plataformes i mercats en línia.
- Alta eficiència operativa utilitzant metodologies àgils i automatitzant tasques rutinàries.
- Construcció de la marca per a obtenir una sòlida reputació, destacant èxits i satisfacció de clients.
- Desenvolupar relacions de llarg termini amb els clients, oferint suport, manteniment i actualitzacions constants.
- Agilitat en el sector, mantenint-se al dia de tendències, metodologies i tecnologies emergents.
- Iniciatives de responsabilitat social que incloguin les comunitats locals, proveïdors sostenibles i ètics, pràctiques que redueixen la petjada de carboni, transparència operativa, economia circular, treball cooperatiu o educació dels clients.
Finalment, cal comunicar-ho mitjançant una estratègia go-to-market: que la nostra marca arribi als potencials clients i puguin distinguir els nostres productes o serveis de la resta mitjançant el seu possicionament.
El model de negoci d'una empresa descriu el tipus de negoci dins del context del mercat, a qui va dirigit, com es vendrà i com s'aconseguiran els ingressos. Per exemple, alguns tipus de models són la fabricació, la distribució, el retail, l'e-commerce, la publicitat, etc.
Una eina molt pràctica per a definir el model de negoci és el Canvas.
Un cop tenim clar el nostre model de negoci, podem concretar-lo i descriure'l al nostre pla d'empresa. És un document descriptiu amb els passos i els números concrets en que es basa la nostra iniciativa, i que ens pot servir per presentar la nostra idea davant dels possibles inversors.
Innovació
L'estratègia més adient per a ser competitiu és la innovació. Innovar és introduir alguna cosa nova o diferent. Fer-ho en un mercat global i dinàmic comporta risc: podem tenir una gran idea, però si no crea valor, no tindrà impacte.
La innovació ens permet:
- Diferenciar-nos, l'avantatge competitiu essencial.
- Millorar la productivitat, i per tant els costos.
- Tenir una certa cultura d'empresa d'innovació contínua.
Les innovacions poden estar dirigides cap a:
- El producte o servei: quines són les característiques.
- El procés: com es gestiona la producció.
- El màrqueting o disseny: quin és el mètode de comercialització i com es concreta al preu, la promoció i la distribució.
- La tecnologia: innovació derivada de la recerca i desenvolupament tecnològic.
- L'organització: quines persones i com estructuren l'empresa.
Quines serien les característiques d'un equip innovador?
- Divers: com ho són les habilitats i responsabilitats necessàries.
- Inquiet: el procés innovador es continu, cal actualitzar-se i estar atent.
- Amb una bona formació en relació a l'àmbit o sector de l'empresa.
Finalment, l'equip humà ha de tenir un pla. El pla d'empresa és un document formal que principalment defineix els objectius del negoci, el mètodes per a aconseguir-los i la seva planificació.
Recursos per a l'emprenedoria
Sabadell
- El Vapor Llonch fa formació, programes d'ocupació, orientació professional, borsa de treball i creació i consolidació d'empreses. També té un observatori de l'economia local.
- L'oficina d'atenció a l'empresa i l'autònom/a de Sabadell es un PAE, que permet crear una societat. També té un centre de promoció empresarial amb oficines i coworking.
- La Cambra de Sabadell disposa de diferents serveis amb cost associat. Tenen un programa d'acceleració de startups innovadores.
Catalunya
- ACCIÓ és l'agencia per la competitivitat de l'empresa de la Generalitat. Està orientada a millorar la competitivitat de l'empresa, oferint una sèrie de serveis per a l'emprenedoria. Té una secció específica per al sector TIC.
- El Canal Empresa de la Generalitat té una sèrie de serveis sobre finançament, innovació, sostenibilitat, etc. També informa sobre la constitució i tràmits per a crear una empresa.
- Catalunya Emprèn.
- Xarxa Emprèn.
- Hi ha un portal d'economia social al departament de treball de la Generalitat. Des d'aquest, es pot gestionar la creació d'una cooperativa o societat laboral.
Estat
El CIRCE és el Centro de Información y Red de Creación de Empresas, un sistema que permet fer els tràmits de creació de societats en línia. Per a crear una empresa de tipus Societat Limitada:
- Anar a un Punt d'Atenció a l'Emprenedor (PAE), on se li assessorarà en tot el relacionat amb la definició del seu projecte empresarial i se li permetrà iniciar els tràmits de constitució de l'empresa.
- Iniciar els tràmits omplint el DUE a través del portal CIRCE. Per a això és necessari disposar d'un certificat electrònic.
Finançament
Alguns tipus de finançament que es poden considerar:
- Ajuts i subvencions
- Premis, concursos i beques
- Capitalització de l'atur
- Microcrèdits
- Fundraising o mecenatge
- Inversors privats (business angels)
- Crowdfunding o micromecenatge
- Banca ètica i coooperatives de crèdit
- Incubadores i acceleradores
Alguns enllaços interessants:
- ACCIÓ, cercador d'ajuts i serveis per a l'empresa.
- ACCIÓ, servei d'assessorament financer per a startups.
- Startup Capital, ajut per a startups tecnologiques.
- Business angels per a emprenedors.
- Finançament i gestió econòmica del Canal Empresa.
- Ajuts i subvencions de l'ajuntament de Sabadell.
Referències
- Ventaja competitiva
- Importance of innovation
- Competitive advantage
- Cómo funciona la máquina económica
Societat
- Entorn empresarial
- Impacte social i ambiental
- Responsabilitat social
- Objectius de Desenvolupament Sostenible (ODS)
- Les cooperatives
- Referències
Entorn empresarial
L'activitat d'una empresa requereix d'una sèrie d'àrees funcionals. Algunes, relacionades directament amb el mercat o competències clau:
- Direcció i control
- Compres a proveidors i emmagatzametge
- Producció
- Comercial o vendes
D'altres, són de suport a les primeres:
- Recursos humans
- Àrea financera (obtenció de recursos econòmics)
- Comptabilitat
- Administració
L'entorn d'una empresa inclou proveïdors, clients, competidors, entitats financeres, administracions públiques, mercat laboral i comunitat.
Cal mencionar el concepte de stakeholder o grups d'interès, persones i grups a què afecta l'activitat de la nostra empresa i que poden influir en el seu funcionament. Per tant, cal tenir-los en compte en les decisions de l'empresa. Principalment inclouen els accionistes i els treballadors, però també proveïdors, clients, consumidors, entitats reguladores i competència.
La empresa responsable ha de dirigir-se estratègicament no només a satisfer als shareholders o accionistes, que busquen el profit de les seves inversions, sinó també dels stakeholders, que componen el teixit social a que afecta l'activitat econòmica.
Impacte social i ambiental
L'activitat empresarial té impactes econòmics, socials i ambientals. Pel fet d'integrar-se a la societat i interactuar amb ella, els comportaments associats poden produir impactes positius i negatius.
Impactes positius:
- Suministre de productes o serveis útils.
- Desenvolupament humà: creen ocupació, formen persones.
- Creació de riquesa, que distribueix entre treballadors, administració (impostos), accionistes i proveidors.
- Les institucions que ordenen l'activitat econòmica busquen el bé comú, i per tant les empreses ho fan subsidiàriament.
Impactes negatius:
- Enfermetats professionals i accidents laborals.
- Reconversió o desaparició de tipus de feines.
- Per als treballadors, productes insegurs o insans.
- Per als consumidors, productes adulterats, insegurs, tòxics, caducats, abusos amb la privacitat de les dades.
- Deteriorament ecològic per contaminació, consum de recursos no renovables, sobreexplotació de recursos renovables, amenaces a la biodiversitat.
A les resposabilitats d'una empresa es poden distingir tres nivells:
- Legal, en referència al cumpliment de la legislació civil, administrativa i penal.
- Ètica, segons la deontologia professional (professió) o cultura corporativa (empresa).
- Moral, en relació al conjunt de regles o principis de comportament d'una persona o col.lectivitat pròpies d'una cultura.
Si el comportament empresarial té una regla clara, n'hi ha prou amb aplicar-la. Si no, podem aplicar el següent enfoc: el correcte és el que produeix més beneficis per al major nombre de persones, i que no viola els drets individuals.
Podem qualificar els comportaments empresarials com a:
- Inmorals: quan només busquem si la acció té beneficis o èxit. Les lleis són obstacles a superar, i no hi ha conducta ètica.
- Amorals: busquem els beneficis o èxit dins de les regles del mercat i la llei. S'interpreta que l'ètica no és un tema empresarial, o no es considera rellevant l'efecte de les accions.
- Morals: es busca l'èxit però només dins dels preceptes de conducta acceptats per la societat. L'empresa té objectius ètics, i interpreta l'esperit de la llei.
Per a l'impacte ambiental, hi ha una tendència a afegir legislació per part dels organismes públics que obliga a les empreses afectades. La difusió de la informació de l'impacte social d'una empresa és voluntària per a una empresa convencional. Aquesta informació permetria als individus prendre decisions de consum i a les empreses i l'administració, decisions de contractació.
Responsabilitat social
La responsabilitat social corporativa o empresarial (RSC/RSE) és el compromís que de forma voluntària assumeixen les empreses i organitzacions per fer-se responsables dels seus impactes, fomentar un desenvolupament sostenible i crear valor econòmic i social.
Els àmbits on calen accions transformadores són els següents:
- Bon govern
- Gestionar amb ètica i transparència.
- Contribuir al benestar de la societat i al bé comú.
- Mesurar la gestió dels àmbits per poder prendre decisions.
- Informar públicament dels avenços.
- Fomentar espais de diàleg amb els grups d'interés.
- Econòmic
- Evitar l'evasió i elusió fiscal.
- Apostar per la compra de proximitat i els recursos locals.
- Afegir criteris no econòmics a les compres.
- Promoure relacions basades en el benefici mutu a la cadena de proveïment.
- Innovar en processos, productes i serveis i invertir en R+D+I.
- Realitzar accions d'inversió socialment responsable.
- Laboral
- Generar ocupació de qualitat: estabilitat i condicions de treball dignes.
- Gestionar les persones amb la seva participació quan els incumbeix.
- Assegurar entorns de treball saludables.
- Desenvolupar les competències i habilitats dels treballadors.
- Gestionar positivament la diversitat.
- Fomentar la reforma horària i la conciliació.
- Ambiental
- Reduir els consums de recursos naturals i energètics.
- Promoure una mobilitat sostenible.
- Reduir i reaprofitar residus.
- Utilitzar productes i serveis respectuosos amb el medi.
- Sumar esforços destinats a la mitigació i l'adaptació al canvi climàtic.
- Apostar per l'economia circular.
- Social
- Impulsar la cohesió social i el compromís amb la comunitat.
- Promoure el consum responsable.
- Integrar la responsabilitat social en l'educació, la formació i la recerca.
- Respectar i protegir els drets humans en tota la cadena de valor.
- Intercanviar experiències i bones pràctiques.
Hi ha una sèrie d'eines de gestió de la responsabilitat social per a PIMES associades a certificacions que permeten avaluar una organització i obtenir un segell reconeixible socialment. En aquesta llista destaquen el Balanç Social o el Pacte Mundial.
Pel fet que cada cop és més important aparèixer social com a empresa responsable socialment, s'està produint el fenòmen del "rentat d'imatge verd" (greenwashing): és l'acció d'una empresa, un govern o un organisme d'usar el màrqueting per a promoure la percepció que els seus productes, objectius o polítiques són respectuosos amb el medi ambient, quan en realitat funciona de manera oposada.
Objectius de Desenvolupament Sostenible (ODS)
Els ODS és una iniciativa de les Nacions Unides amb una agenda fins al 2030. Són una crida universal a l'acció per posar fi a la pobresa, protegir el planeta i millorar les vides i les perspectives de les persones a tot el món.
Estan dirigits a la societat, de forma general. Aquesta és l'aproximació empresarial que fan les Nacions Unides als ODS per a cada objectiu:
- Fi de la pobresa. Donar oportunitats laborables per a grups vulnerables, amb condicions dignes i impactant positivament a les comunitats locals.
- Fam zero. Investigar la tecnologia agrícola, pràctiques sostenibles a la cadena de subministrament i accés a aliments sans i suficients.
- Salut i benestar. Plans de seguretat i salut laboral per als treballadors i cadenes de valor, evitant impacte negatiu de les seves operacions i contribuir positivament sobre el benestar.
- Educació de qualitat. Formació dels empleats i grups d'interés, inversió en educació per a millorar les oportunitats laborals i salaris.
- Igualtat de gènere. Garantir els mateixos drets i oportunitats laborals a la dona, programes d'empoderament econòmic.
- Aigua neta i sanejament. Gestió sostenible de recursos hídrics en la elaboració de productes i serveis, foment de la millora de la gestió sostenible en la cadena de valor.
- Energia asequible i no contaminant. Inversió en fonts d'energia neta, tecnologies que redueixen el consum elèctric, projectes per electrificar comunitats desfavorides.
- Feina decent i creciment econòmic sostenible. Garantir les condicions dignes de treball, foment de la ma d'obra vulnerable.
- Industria, innovació i infrastructura. Innovació per a la sostenibilitat, processos industrials sense impacte sobre el medi, infrastructures sostenibles i resilients, accés TIC a tots els treballadors, tecnologies eficients i sostenibles.
- Reducció de les desigualtats. Condicions laborals dignes, redistribució igualitària dels salaris, mecanismes per evita l'evasió fiscal, projectes de cooperació al desenvolupament.
- Ciutats i comunitats sostenibles. Innovació per al desenvolupament de ciutats sostenibles i intel·ligents, mobilitat sostenible, reducció de consum energètic i aigua.
- Producció i consum responsables. Ús eficient de recursos a la cadena de valor, retirar productes i serveis amb consum excessiu, impuls d'energies renovables, reutilització hídrica, reduir la contaminació, formació en pràctiques de producció i consum sostenible, combatre el desperdici alimentari, ecoetiquetat.
- Acció per al clima. Reduir emissions de gasos, impulsar energies renovables, innovació al medi.
- Vida submarina. Reduir la contaminació de mars i oceans, promoure la pesca sostenible, ajustar-se al dret internacional.
- Vida d'econsistemes terrestres. Evitar l'impacte sobre els econsistemes i hàbitats en les operacions de l'empresa, respectar la normativa corresponent, integrar la conservació de la diversitat biològica.
- Pau, justícia i institucions sòlides. Incorporar el respecte als DDHH i transparència en l'organització, evitar quasevol tipus de violència sobre menos i grups volnerables, impulsar l'estat de dret.
- Aliances per a assolir els objectius. Aliar-se amb el sector públic, la societat civil, universitats i altres empreses per a realitzar projectes en pro dels ODS.
Les cooperatives
La organització amb responsabilitat social per antonomasia té la forma jurídica de cooperativa. Entre els principis que identifiquen la cooperativa cal destacar:
- La democràcia empresarial que defineix la seva gestió.
- La participació econòmica del socis.
- L'interès per proporcionar formació i informació als socis.
- La millora de la situació econòmica i social, tant dels components com de l'entorn comunitari.
Les cooperatives poden ser de diversos tipus. Les més addients per a produir béns o serveis són les cooperatives de treball associat. També hi ha les cooperatives de serveis per a professionals per compte propi. Finalment, hi ha dues condicions que poden tenir les cooperatives: ser d'iniciativa social o ser sense ànim de lucre. En funció d'aquestes característiques, poden tenir beneficis concrets.
Referències
- Economia social
- La responsabilidad social de la empresa
- Responsabilitat social
- Recursos formatius de responsabilitat social
- El sector privado ante los ODS
- 17 objectius per a les persones i el planeta
- Carlos Taibo: "El planeta se nos va y es necesario frenar de inmediato la locomotora del crecimiento"
- ¿Es el decrecimiento económico una alternativa real?
- Los fallos del PIB y sus alternativas
L'empresa informàtica
- Software a la economia
- Estratègia de negoci
- Deute tècnic
- Model de negoci
- Impacte social
- Impacte ambiental
- Referències
Software a la economia
El software és omnipresent:
- Substituint negocis tradicionals, com llibreries, publicitaris, música, telecomunicacions, selecció de personal, serveis financers, etc.
- Menjant-se la cadena de valor de diferents negocis, tot i no substituir-los, com a la fabricació de cotxes, la carrera espacial, la logística i la distribució, etc.
El progrés humà podria veure's com a quatre revolucions industrials:
- El vapor, l'aigua i la producció mecànica.
- La divisió del treball, l'electricitat i la producció massiva.
- L'electrònica, la informàtica i la producció automatitzada.
- L'anàlisi de dades, els dispositius mòbils, la intel·ligència artificial, el machine learning, la robòtica i la genòmica.
No sempre podem fer investigació des de l'empresa. Però l'ús de les tecnologies de propòsit general ens dona un marc d'innovació que podem integrar en el nostre negoci.
La tecnologia ha transformat els negocis i també la societat, produint canvis disruptius a cadascuna d'aquestes revolucions que han afectat les persones i les seves feines.
Estratègia de negoci
La innovació permet a l'empresa entrar al mercat i adaptar-se als canvis. L'empresa software ho pot fer de diverses maneres:
- Fent la creativitat un hàbit (cultura corporativa).
- Tenir un cicle de desenvolupament (SDLC) àgil.
- Tenir un espai de treball (físic o virtual) funcional, flexible i mòbil. Tenim eines de gestió de codi i DevOps al nostre abast.
Què necessita el món empresarial del software?
- Hi ha negocis completament software:
- Creació, adaptació, ampliació i manteniment d'aplicacions off-the-shelf i a mida.
- Sharing economy.
- Software as a Service.
- Tenim la tasca de la digitalització d'empreses:
- Presència a internet (web, xarxes).
- Estratègia de comunicació i màrqueting.
- Venda online.
- Digitalització dels processos de gestió (CRM, ERP, treball col·laboratiu, teletreball).
- Tenim feina a les activitats de la cadena de valor:
- Serveis dins de les empreses, especialment en automatització, qualitat, anàlisi de dades i business intelligence.
- Interacció entre empreses (B2B), com adaptació de protocols, API Rest.
La nostra estratègia go-to-market hauria d'incloure:
- La visibilitat d'un equip innovador i ben format.
- El nostre portfolio de solucions i creativitat.
- Solucions sensibles a l'èxit del client a l'estructura de costos (per ús).
- Bona comunicació escrita en les propostes tècniques, justificades i amb detall.
- Codi font lliurat al client amb la promesa que podrà ser assumit per qualsevol altre proveidor.
Deute tècnic
El deute tècnic és el resultat de decisions tècniques dolentes o subòptimes que finalment generen problemes per a mantenir i expandir una solució. Com a integrants de l'economia del software, tenim una responsabilitat en reduir aquest deute.
La raó del deute tècnic pot ser:
- El resultat d'una decisió basada en paràmetres de negoci i no sostenible.
- El resultat de fer un mal disseny i la seva implementació del codi.
En ambdós casos, el resultat és el mateix: un codi que no s'entén, que no és robust, que està mal documentat, que provoca infelicitat als programadors i que molt difícilment es pot modificar o estendre.
Què podem fer per reduir el deute?
- Tenir clara l'estratègia tècnica inicial. Fer una bona avaluació de les possibilitats, i fer-ho amb un objectiu ben definit.
- Tenir un responsable d'arquitectura.
- Dissenyar amb la seguretat com a requisit.
- Pensar com es podrien substituir els components o llibreries de la nostra solució.
- Mantenir la refactorització a petita escala dins del procés, millorant el codi.
- Tenir testos de regressió automatitzats.
- Millorar la cobertura dels tests.
- Reescriure codi, si cal. És un moviment perillós, però de vegades necessari.
- Fer una bona documentació.
- Tenir bons canals de comunicació per a gestionar problemes.
Model de negoci
El nostre model de negoci software ha de descriure el tipus de negoci dins del context del mercat, a qui va dirigit i com s'aconseguiran els ingressos necessaris.
El model de negoci descriu com fa una organització per a crear, lliurar i capturar valor en un context econòmic, social o cultural.
Si utilitzem el canvas com a eina per a definir el model de negoci, podem trobar els següents aspectes.
Segments de clients
Podem caracteritzar els client segons una sèrie de paràmetres.
Segons l'audiència:
- B2B (venda a altres empreses)
- B2C (venda a usuari final)
- C2C (marketplace).
Segons l'àmbit del producte:
- Off-the-shelf ("caixa"): generalista, solució global. Pot permetre customització o implementar personalització.
- A mida: fet a mida, per a un problema concret del client.
- Híbrid: producte off-the-shelf que permet desenvolupaments a mida gràcies a un API o similar. Pot ser obert o tancat.
Segons la plataforma destí:
- Per a una o diverses plataformes: Android, iOS, etc.
- Plataforma agnòstica. Habitualment basada en web.
Segons la interacció dels usuaris:
- One-to-many (clients)
- Many-to-many (usuaris productors i usuaris consumidors).
Segons el model de llicències:
- Propietari
- Open-source
Proposta de valor
Explicar la proposta de valor: una afirmació que identifica els beneficis clars, mesurables i demostrables que els clients obtenen en comprar cadascun dels productes o serveis. Caldria explicar-ho per cada segment.
En software significa explicar almenys les funcionalitats, el rendiment, l'arquitectura i el model de suport.
Canals
Fases:
- Coneixement: web, màrqueting online.
- Avaluació: freemium, trial.
- Compra: contracte, e-commerce, store.
- Lliurament: on-premise vs off-premise (cloud) vs híbrid.
- Suport: issue/bug tracker, CRM.
Segons el lliurament:
- On-premise: el software s'instal.la i funciona a les instal.lacions del client.
- Cloud-based: el software funciona al núvol o a un proveïdor de allotjament (SaaS).
- Híbrid: barreja els dos anteriors. Hi ha instal.lació, però també es compta amb el núvol per al seu funcionament.
Relació amb els clients
Segons el model de negoci. Podem utilitzar eines CRM integrades en el nostre ERP, comunitats a les xarxes, auto-servei.
Ingressos
Podem tenir un o més fluxos:
- Aplicacions de pagament. Els clients paguen per instal·lar un producte.
- Publicitat a l'aplicació. L'aplicació és gratuïta, però veneu llocs d'aplicacions per a publicitat.
- Compres des de l'aplicació. L'aplicació és gratuïta, però guanyes venent productes o serveis mitjançant una aplicació.
- Subscripcions. Els usuaris paguen anualment o mensualment una quota de subscripció.
- Model d'ingressos de programari basat en l'ús. Els clients paguen només pel que fan servir.
- Desenvolupaments a mida. Els clients paguen per desenvolupar funcionalitats a mida.
- Càrrecs per suport, serveis empresarials i consultoria.
Activitats clau
- Programació.
- Suport i consultoria.
- Anàlisi, venda o accés a dades.
- Optimització interna i innovació.
- Formació contínua.
Recursos clau
- El nostre codi, que pot ser propietari o open source, i llibreries de tercers.
- Les nostres dades (com a actius).
- Programadors que donen valor a l'empresa.
- Ordinadors.
- Local físic vs teletreball.
- Subscripcions a serveis o comptes de desenvolupament.
Costos
Revisar els costos dels recursos clau.
Socis clau
- Dependència d'empreses tecnològiques.
- Store amb les seves normes i tecnologies.
- APIs de tercers.
Impacte social
Els codis deontològics que hi ha a les referències d'aquest document ens donen una visió general de l'impacte de la professió de desenvolupament de software. La responsabilitat podria veure's en tres àrees: negoci, tecnologia i societat.
Negoci
La pregunta a fer-se és: segueixo l'estratègia correcta? Aquesta estratègia ha de ser compatible a la de les altres dues àrees esmentades.
Hem d'assegurar-nos de complir tota la llei i normativa en relació a la societat. En particular:
- El reglament general de protecció de dades europeu (RGPD).
- La llei orgànica de protecció de dades personals i garantia dels drets digitals, que compatibilitza la legislació espanyola amb el RGPD.
- La llei de serveis de la societat de la informació i del comerç electrònic.
Tecnologia
Ens hem de preguntar: utilitzo l'eina correcta per la feina? És quelcom nou o reinventem la roda? Estic generant deute tècnic? Aquestes decisions impactaran al mercat del desenvolupament software, vist com un col.lectiu que interactua i acaba reprenent o col.laborant en solucions tècniques.
Societat
Ens preguntarem: millorem les vides de les persones? Pot utilitzar-se de forma nociva? Exclou persones? És ètic? És segur? A qui pertanyen les dades?
En aquesta àrea ens podem preguntar:
- Si utilitzem les eines i solucions més responsables socialment, com per exemple, el codi obert.
- Si respectem el dret a la privatitat en el tractament de les dades de les nostres solucions.
- Si seguim les línies universals de l'IA.
Impacte ambiental
Computació verda: ús eficient dels recursos informàtics, minimitzant l'impacte ambiental. Aproximacions:
- Longevitat dels productes, ja que el procés de fabricació és la part més significativa de l'ús de recursos naturals.
- Disseny eficient dels data centers.
- Eficiència del software (algorismes, assignació de recursos, virtualització, servidors de terminals).
- Gestió de energia amb l'ús de components no utilitzats, reduir voltatges, parar màquines, fonts d'energia més eficients, etc.
- Reciclatge d'equips per ser reutilitzats.
- Cloud computing (virtualització) versus edge computing (més a prop).
- Teletreball, reduint el transport.
En particular, programació verda:
- Minimitzar l'emissió de CO2.
- Dissenyar les aplicacions eficientment per reduir el consum d'energia.
- Consumir electricitat amb la intensitat de CO2 mínima (mix de fonts).
- Construir aplicacions que siguin eficients amb el hardware, estenent la seva vida.
- Maximitzar l'eficiència energètica del hardware, reduïnt el nombre de servidors amb la major ràtio d'utilització.
- Reduit la mida i la distància recorreguda de les dades per la xarxa.
- Construir aplicacions conscients del CO2, que permetin gestionar la demanda i moure-la a regions o moments de menys intensitat.
- Per poder optimitzar, mesurar el CO2, l'energia, el cost, l'ús de xarxa i el rendiment.
Referències
- Why software is eating the world
- Social Responsibility Impacts Software Development Processes
- How to recognize exclusin in AI
- Principles of Green Software Engineering
- Best Practices of Sustainable Software Development
- Codi Deontològic
- Código Ético y Deontológico de la Ingeniería Informática
- Programming ethics
Proposta de valor
- Punt de partida
- La idea de negoci
- Generació d'idees
- Proposta de valor
- Canvas del model de negoci
- Pla estratègic
- Referències
Punt de partida
Autoocupació: activitat professional o empresarial generada per una persona, i que l'exerceix de forma directa pel seu compte i risc.
Qualitats de les persones emprenedores:
- Creativitat
- Iniciativa
- Responsabilitat
- Autonomia
- Assumpció de riscos
- Competències socials
- Competències personals
Intraemprenedoria: són treballadors que des del seu lloc de treball a una empresa on no són propietaris desenvolupen i posen en pràctica les seves qualitats emprenedores en benefici de l'empresa per a la que treballen.
La idea de negoci
La idea és el producte o servei que es pretén oferir al mercat.
Cal avaluar-la i veure la seva viabilitat:
- és útil
- es diferencia de la competència, creant valor afegit
- genera innovació: és nou, millora un producte/servei existent o el seu procés de fabricació
- és rendible
Creativitat i innovació
Origen de la idea innovadora:
- necessitat no satisfeta
- factor diferenciador
- innovació en tecnologia
- aprofitar la pròpia formació o experiència
- repetir experiències alienes
- cercar referències en internet
Podem validar la idea amb preguntes clau:
- quin valor aporta la nostra idea o producte?
- què necessiten els meus clients potencials?
- com és el sector on vull emprendre?
- quin segment de clientes és rellevant per a nosaltres?
- com puc testar la validesa de la meva idea?
- quin tipus de comunicació volem establir amb el nostre entorn?
- què esperem aportar a la societat?
- quins canals de distribució i promoció són rellevants?
Innovació i desenvolupament econòmic
Evolució del factor generador de riquesa i desenvolupament a la història:
- la terra dedicada a l'agricultura
- amb la revolució industrial, els recursos energètics
- actualment, el coneixement i la innovació
Factors importants:
- I+D+I: investigació, desenvolupament i innovació.
- el desenvolupament integrat: econòmic, social i sostenible.
Com podem innovar
Podem innovar:
- en el producte (total o evolució)
- en el procés (despeses de producció i distribució, millora de la qualitat)
- en màrqueting (disseny, envasat, posicionament, promoció)
- en l'organització de l'empresa (canvis en les pràctiques i procediments o al lloc de treball)
Valoració de la idea de negoci
Un possible esquema per validar seria:
- fer un estudi inicial: possibilitats que ofereix el mercat, forats que hi ha, cercar fonts, imaginació
- fase de consulta: cercar recolzament d'experts, de possibles proveïdors i clients, normativa vigent
- anàlisi de l'acollida: en funció de com de positiva hagi estat, veure si cal reconsiderar la idea o descartar-la
- presa de decisions: decidir què farem del projecte
- posada en pràctica: implementar-la
Generació d'idees
Brainstorming: la Tempesta d'Idees.
Scamper: sobre un element que es desitja millorar, buscar idees tenint en compte les preguntes derivades:
- Substituir, Combinar, Adaptar, Modificar, Buscar altres usos, Eliminar, Canviar la forma.
Sinèctica: Generació d'idees per analogia.
Pensament lateral: No sempre hem de pensar de forma lògica. Podem fer un enfocament indirecte i creatiu.
Els sis barrets (Six thinkings hats): Analitzar el problema fent ús de sis tipus de pensament diferents:
- objectiu (fets, números, verificació)
- intuïtiu (emocions, sentiments estètics)
- creatiu (alternatives, atzar, extrems)
- negatiu (riscos, perills, imperfeccions)
- positiu (optimisme, futur, somnis)
- control (síntesi, organització, conclusions)
Proposta de valor
La proposta de valor és el conjunt de productes i serveis que creen valor per a un segments de mercat específics. L’objectiu és solucionar els problemes dels clients i satisfer les seves necessitats mitjançant propostes de valor. Quin problema ajudem a solucionar? Quin valor oferim als nostres clients? Cal plantejar-ho des de la perspectiva de “què vol comprar el nostre client” versus “què venem”.
Quan es plantegem el model de negoci, identifiquem tres preguntes:
- Com? Activitats relacionades amb la producció.
- Què? La nostra oferta. Aquesta és la que respon la proposta de valor.
- Qui? Activitats relacionades amb la venda.
Per a arribar al Què, podem fer-nos les següents preguntes:
- Què és el que desitjo oferir als clients?
- Quines necessitats dels potencials clients cobriré amb aquest producte o servei?
- Què li proporciono al client que no s’estigui oferint per una altra empresa del mercat?
Donant resposta amb aquestes qüestions podrem establir la nostra proposta de valor. D’aquesta forma podrem establir els criteris de model de negoci, següents:
- Seleccionar els clients potencials als que dirigirem l’oferta
- Crear utilitat per als potencials clients.
- Diferenciar-nos de la competència.
- Aconseguir i conservar als clients
- Com se seleccionaran els clients
Canvas del model de negoci
Per saber com funciona el model de negoci canvas has de saber que és un llenç format per una sèrie d'elements que connecten les diferents parts de l'estructura d'un pla de negoci. És una eina útil i un format cada vegada més sol·licitat. El model canvas per emprendre està compost per 9 fases descrites a continuació:
- Segments de clients: Respon a la pregunta a qui es dirigeix el nostre producte o servei. Descriu el públic objectiu i les seves característiques.
- Proposta de valor: En aquest apartat es tracta d'enfocar els beneficis del teu servei o producte, quina diferència teu pla de negoci al d'altres, quin és el teu punt diferenciador davant la competència.
- Canals de distribució: Vies a través de les quals anem a comunicar la nostra proposta de valor. Els canals que proposa el model de negoci de canvas són: canals propis o externs, directes o indirectes. Aquest segment inclou la descripció de l'efectivitat que generen aquests canals: la notorietat, avaluació, comunicació, distribució i venda.
- Fonts d'ingressos: Com generem els beneficis perquè funcioni el pla de negoci. Aquí s'ha de diferenciar d'ingressos i guanys per no obtenir errors de pressupost.
- Recursos clau: enumera els actius més importants perquè el pla de negoci funcioni. Són els recursos físics, financers, humans o immaterials com les patents o coneixements.
- Relació amb clients: La relació podrà ser personal o automatitzada. Es tracta de tenir en compte en el model de negoci la fidelització i captació de clients i l'estimulació de les vendes.
- Activitats clau: processos claus per al funcionament de l'activitat que es va a exercir. Segons el model canvas les activitats clau d'una negoci són tres: producció, solució de problemes i plataforma.
- Socis clau: aquesta part del pla de negocis amb el model canvas remarca els partners i proveïdors necessaris perquè la idea de negoci funcioni.
- Estructura de costos: segons el model canvas són les despeses en què s'incorre durant el procés de generar valor, és a dir, els costos que genera el negoci. El model de negoci canvas els divideix en: costos fixos i variables, economies d'escala i economies de camp.
El canvas social
- Segments de clients: hauríem de plantejar-nos com generem valor social en el segment de clients i com millorem l'entorn social, per exemple: Que el nostre projecte estigui adreçat a algun col·lectiu en risc d'exclusió social.
- Proposta de valor: hem de plantejar-nos com la nostra proposta fa una producte al servei de les persones, or exemple, podem fer que el nostre producte o servei sigui accessible a persones amb diversitat funcional).
- Canals de distribució: estudiar com el canal de distribució pot ser positiu per a la societat, per exemple si faig lliurament a domicili, la faig amb bicicleta per no contaminar.
- Fonts d'ingressos (situació econòmica a l'inici): és important tenir present que el consum responsable és una màxima per millorar el món en què vivim, ia més pot ser un benefici per al nostre començament, això no vol dir baixar la qualitat, ni el valor que volem aportar, només es tracta d'ajustar bé els números. Per exemple, si volem que el nostre producte sigui ecològic, segurament haurem d'invertir una mica més, però no obstant això el cost social que aportem a la societat també té un gran valor que no és econòmic.
- Recursos clau: serà clau si ajustem molt bé les necessitats, tant des d'un punt de vista d'eficiència, com des d'un punt de vista de consum responsable. També ens ajudarà pensar que si estigueu-vos recursos els trobem al mercat local i de proximitat estem potenciant l'economia dels meus veïns, als que conec i als que tinc confiança. O per exemple en l'aspecte dels recursos humans, el nostre empleats poden formar part d'algun col·lectiu d'inserció, o simplement podem tenir una organització interna democràtica com és el cas de moltes cooperatives.
- Relació amb clients: podem fer que la nostra estratègia de comunicació tingui un impacte social positiu, per exemple si anem a fer la comunicació en paper, podem usar paper reciclat.
- Activitats clau: podem fer que la nostra proposta de valor i per tant la nostra activitat principal tingui un enfocament ecològic o de compromís amb l'entorn local, per exemple que els productes que venc siguin biològics, o en el sector serveis que el meu servei pugui arribar també a un sector de la població que no té recursos econòmics.
- Socis clau (col·laboradors): es planteja que la relació amb l'entorn proper és essencial, potencia l'economia local ens afavorirà i crearà sinergies diferents de consum al nostre voltant. Quan coneixem els canals de distribució, quan els nostres clients coneixen de prop als nostres proveïdors i als nostres col·laboradors, la confiança és un element clau per al consum responsable.
- Estructura de costos (situació econòmica durant el projecte): és important plantejar-se l'enfocament no lucratiu, que ens ve a dir que el projecte cobreix els sous dignes i coherents de les persones treballadores, i que els beneficis es reinverteixen en benefici del projecte o es destinen a alguna obra social o ambiental. També podem plantejar-nos en aquest punt, on guardo els diners mentre no el faig servir i assegurar-me que està en un banca ètica, i no és invertit en caps amb els quals no estic d'acord.
Pla estratègic
Introducció
Molts cops la mateixa dinàmica de la companyia i l’entorn orienta a l’empresa cap a unes estratègies determinades sense necessitats de fer cap pla. Aquestes són les estratègies emergents que, en general, serveix per anar seguint el ritme del sector. En canvi, les estratègies deliberades són les que obtenim del nostre pla estratègic, i són aquestes les que ens porten a canvis importants amb estratègies ofensives.
Algunes consideracions a tenir en compte al definir l’estratègia, són:
- Estan en línia de la identitat de l’organització
- S’enfoquen a mitjà o llarg termini, a partir de 3 anys vista
- Impliquen la posada en marxa una quantitat significativa de recursos
Una bona forma de començar la introducció és definir la situació de l’empresa en el moment d’elaborar el document.
Cal també concretar quin termini cobreix el Pla estratègic. Aquest ha de ser a llarg termini sabent que:
- Pressupostos – s’elaboren a 1 any vista
- Planificació – és la definició de l’estratègia
- Estratègia – és la gestió a llarg termini
Es considera acceptable un Pla Estratègic a més de 3 anys, ja que no pot coincidir amb els pressupostos.
La diferència d’on som i on volem arribar és el Gap estratègic. Per tant cal gestionar el gap per poder evolucionar d’on estem avui. Cal aspirar a un demà ambiciós perquè normalment arribarem un punt més avall.
Visió, missió i valor
La missió, la visió i els valors han d’estar clarament redactats al Pla estratègic, ja que ha de donar sentit al treball diari que realitzi l’empresa. Han de definir un marc prou ampli com per poder ser vàlids al llarg de la vida de la empresa, tot i que poden es poden revisar però no ser subjectes de grans i continuades modificacions. En aquest cas significaria que no estan ben redactats.
Visió
Associada al somni. Què volem ser, on volem arribar.
Respon a les preguntes:
- Què volem aconseguir com a organització?
- Com s’enfrontarà l’empresa al canvi?
- Com es diferenciarà de la resta?
- Com s’aconseguirà ser competitiva?
És possible que en els seus inicis l’empresa passi per dificultats però tingui una visió molt ambiciosa a la que arribar al llarg de la seva trajectòria. La visió dóna a l’empresa una fita per assolir que aporta sentit als esforços que desenvolupen les persones que la integren.
Algunes recomanacions a l’hora de redactar-ho:
- Ha de incloure dos components: una meta ambiciosa a complir en 10 – 30 anys però també una descripció palpable del futur.
- Hem de pensar que ha de ser un punt d’orientació. Ha d’apel·lar tant a la intel·ligència com a les emocions dels treballadors.
Validació:
- Defineix un futur desitjable?
- Motiva?
- Es clara?
- Es concisa?
- Es memorable?
Walmark: ser líder mundial del retail.
Missió
És la raó d’existir de l'organització. Habitualment és útil definir quines línies no volem adoptar per poder obrir ventall al què sí.
Respon a les preguntes:
- Què fem?
- Per a qui ho fem?
- Quines necessitats satisfem?
- Què valoren els nostres clients?
- Com ens diferenciem de la resta?
Walmark: ajudar a estalviar perquè vivim millor (lideratge en costos: economia d’escala, distribució tecnificada, integració tecnològica amb els socis, coneixement dels clients, cultura).
Valors
Els valors ens indiquen quines són les nostres formes particulars per seguir el camí que ens porta a la visió.
Proporcionen un criteri en el moment de prendre decisions incertes, que en alguns casos podrien ser una solució fàcil però que portarien a trair els nostres valors.
Referències
- La cadena de valor de Michael Porter (vídeo 6 min)
- Business Model Canvas Paso a Paso + 2 Ejemplos (vídeo 15 min)
- El canvas social
- Qué es una Startup y cómo funciona éste nuevo modelo de negocio
- Lista Emprendedores: las 50 startups con más futuro
- 17 ejemplos inspiradores de misión, visión y valores de empresas
Vídeos:
- Salvados. La Fageda, cuando negocio y ética van de la mano
- Business Model Canvas Paso a Paso + 2 Ejemplos
- Elevator pitch. Tienes 20 segundos - eduCaixa
- Aprende a hacer la visión, misión y valores en menos de 5 minutos
- Triodos Bank en Buenafuente (La Sexta). Entrevista a Joan Antoni Melé
- El comercio justo en 6 pasos
Estudi de mercat
- Segmentació
- Estudi de mercat
- Referències
Segmentació
1. Segmentació, Beneficis i Característiques de la segmentació
Segmentació de mercat.
Un mercat està format per empreses i consumidors. En els mercats de consum convé segmentar els consumidors en grups de consumidors que tenen les mateixes característiques, és a dir, en grups homogenis.
L’objectiu de la segmentació és aplicar una estratègia comercial diferenciada a cada segment, fet que aportarà més efectivitat a les nostres accions.
La segmentació de mercats permet diferenciar el producte segons les necessitats de cada grup de consumidors. Per exemple: una empresa fabricant de cotxes ofereix: cotxes familiars, cotxes esportius, cotxes compactes urbans, etc. un per cada tipus de consumidor.
Beneficis de la segmentació:
Segmentant coneixem millor els nostres consumidors i disposem de més informació per prendre decisions de màrqueting. Beneficis:
- Permet identificar els segments de mercat més atractius: sigui per creixement de mercat o perquè ens permet identificar segments que estan insatisfets. Després el que caldrà fer és establir prioritats per decidir quins segments satisfer primer.
- Facilita l’anàlisi de la competència. Si la competència ha segmentat i té diferents tipus de productes podem saber què cobreix i identificar millor les seves accions i trobar forats de mercat.
- Permet adaptar el producte a les necessitats del consumidor i satisfer-lo millor: nou producte, nou disseny, reposicionament...
Característiques d’un segment:
Un segment ha de ser:
- Mesurable: Hem d’identificar la mida del mercat (volum de vendes potencials) i el poder de compra dels seus consumidors: renta disponible)
- Accessible: Cal determinar on podem vendre i publicitar el producte.
- Substancial: Cal que tingui una mida mínima per tal que sigui rendible.
- Estable al llarg del temps: Hem de poder rendibilitzar la inversió.
- Cada segment ha de ser diferent dels altres (Ex. pel tipus d’ús del producte, pel comportament de compra...).
2. Criteris de segmentació
Criteris de segmentació
Quan segmentem un mercat podem dividir-lo segons uns criteris:
- Generals: Que no tenen relació amb el producte.
- Específics: Que tenen relació amb el producte.
- Objectius: fàcils de quantificar.
- Subjectius: No tan fàcil de quantificar.
Criteris objectius
- Generals
- Demografia: Unitats familiars, població urbana/rural, edat, sexe.
- Socioeconòmics: Nivell econòmic.
- Específics
- Ús del producte:
- Continu/1 sol cop. Ex. lentilles, càmeres.
- Familiar /individual (menjar precuinat).
- Ús freqüent o esporàdic. Ex. crema solar o crema diària per la pell.
- Lloc de consum o de compra: cosmètics de viatge o de casa, ex. consumidors de llibres en màquines del metro o consumidors de llibres en llibreries, Cola-Cao en sobres per als bars i Cola-Cao en pot per a les famílies).
- Fidelitat a la marca: programa de punts frequent flyer per aquells clients que són fidels.
- Categoria d’usuaris: nou client /antic/ regular... ex. promoció per a nous clients d’un banc o de telefònica o per a subscriptors d’un diari.
- Ús del producte:
Criteris subjectius
- Generals
- Personalitat: Extravertit / introvertit, prudent /arriscat, rata /generós, confiat / desconfiat, perfeccionista / indiferent...
- Estil de vida: regularitats que s’observen en la conducta de les persones en diferents situacions canviants de la seva vida. Forma de viure. Com ho calculem: Segons les opinions de la gent, els interessos (salut / oci) o la cultura (família, sexualitat, treball...) Veiem com gasten els diners i el temps en activitats, treball, compres... Ex. estils de vida: JASP.
- Específics
- Actituds, percepcions, preferències dels consumidors: Ex. grau de risc que accepten: cotxes segurs... Percepcions sobre l'obesitat (per això fan productes light), percepcions sobre el medi ambient (productes reciclables).
- Beneficis o avantatges que busquem en el producte... el motiu de la compra. Ex. diferents segments en funció del que esperen per la compra d’una minicadena hi-fi... un de més tècnic es preocupa pel so, un altre voldrà un disseny especial, un altre valorarà que sigui econòmic...
3. Estratègies de segmentació
Hem vist que podem segmentar el mercat per criteris generals (que no tenen a veure amb el producte, com p. ex. la demografia) i específics (relacionats amb el producte: com el seu ús: ampolla familiar o d’ús individual) i a la vegada per criteris subjectius (difícils de mesurar, com per exemple la personalitat) i objectius (com per exemple la categoria d’usuari).
Estratègia indiferenciada
Consisteix a aplicar la mateixa estratègia comercial a tots els segments que hem determinat. Busca cobrir les necessitats comunes més que trobar les diferències.
Considera que les diferències existents entre cadascun dels segments no són suficientment importants com per fer estratègies diferents.
La distribució i la publicitat són generalment massives.
Els costos de l’estratègia són menors que d’altres, ja que estalviem en termes de producció (productes únics, menys colors, formes...), distribució (menys temps negociant amb diferents canals) i comunicació (no cal adaptar-la a cada segment).
L’inconvenient d’aquesta estratègia és que no és optima en els mercats on hi ha molta competència o quan existeixen segments molt heterogenis. I avui en dia la majoria de mercats ja estan molt segmentats i costa trobar un producte que s’adapti a grups heterogenis. És difícil competir amb altres empreses que sí que fan la diferenciació i satisfan millor als clients.
Per exemple, en un primer moment Coca-cola només oferia una versió del seu producte, esperant que fos del gust de tothom, més endavant va anar adaptant el producte a diferents segments: light, sense cafeïna, sense cafeïna i light...
A més a més, l’aparició de nous mitjans de comunicació i canals de distribució ha ocasionat que aparegui la possibilitat de segmentar més el mercat i que sigui més difícil aplicar una estratègia comercial única. No farem igual una campanya per internet que en un gran hipermercat o en una botiga detallista.
Estratègia de segment diferenciada
L’empresa analitza el mercat i estableix diferents segments als quals aplicarà una estratègia concreta i diferenciada una de l’altra per cobrir millor les necessitats dels clients.
Aquest és el cas de les agències de viatges que tenen en compte el Cicle de vida de les famílies (el cicle de vida familiar significa que hi ha diferents etapes en la vida familiar normal):
- Etapa de solters: persones joves sense vincles matrimonials.
- Parelles casades joves sense fills.
- Niu ple. Parelles casades joves amb fills.
- Nius plens. Parelles casades, de major edat, amb fills encara dependents.
- Niu buit: parelles casades de major edat sense fills dependents.
- Persones de major edat que viuen soles. Encara treballant o ja jubilades.
Podem adaptar una estratègia comercial diferent per a cadascun d’aquests segments.
Avantatges: l’empresa esdevé més eficient perquè es concentra esforços en satisfer al client adaptant el producte, escollint el canal i realitzant una comunicació específica per al client. Quan el mercat està molt segmentat no trobem tants competidors perquè adaptar-se als segments implica un cost elevat, guanyem doncs, quota de mercat i fidelitat envers la marca. Si som forts en cada un dels segments, podem aconseguir ser més eficients que dirigint-nos a la totalitat del mercat amb un sol producte, ex. Procter&Gamble amb el sabó de la roba.
Inconvenients: Cost ja que gastem més en adaptar la producció, en controlar els diferents canals i en imaginar diferents estratègies de comunicació. Una excessiva segmentació ens pot portar a confondre al consumidor, canibalisme entre productes i una disminució de la rendibilitat (no hi ha economies d’escala).
Estratègia de segmentació concentrada: nínxols
L’empresa decideix atendre a un segment o a un subsegment del mercat però no a tots perquè no té la capacitat interna suficient o perquè les condicions del mercat no són favorables. Un subsegment pot ser per exemple, dins de la categoria de cotxes utilitaris, el subsegment pick-up i el subsegment utilitari esportiu.
Els segments són generalment grans i els subsegments o nínxols són més petits i normalment atrauen menys competidors.
Pex. Bentley es dirigeix a un subsegment: cotxe de luxe, alta qualitat, bon servei i status.
L’empresa es dirigeix al segment on té un avantatge competitiu que d’altres no tenen on que no volen utilitzar perquè no tenen prou experiència o no consideren que sigui un mercat en creixement.
La producció, distribució i comunicació són molt específiques per al segment.
Avantatge: Aconsegueix una bona quota de mercat perquè s’especialitza.
Inconvenient: Estratègia molt sensible als canvis de les preferències del consumidor i a l’aparició de nous competidors.
4. Màrqueting mix, Distribució o Comunicació
A partir de la concreció de les variables del màrqueting mix (producte, preu, distribució i comunicació) podem segmentar segons els criteris corresponents.
Per producte
Podem oferir diferents models d’un mateix producte diferenciats per:
- Hàbit d’ús: envàs familiar o individual: aigua
- Lloc d’ús: a casa, de viatge: raspall de dents
- Marca: creem una segona marca com per exemple Sony i Aiwa.
- Complexitat del producte: mòbils normals o avançats.
Per preu
Podem oferir un preu diferent a cada segment, sigui per una oferta temporal o per una categoria d’usuaris especial: ex. Els subscriptors tenen un 20% de descompte en entrades al teatre o, si contractes un pac de telefonia abans d’una data X, tens una bonificació. Podem fer una compra en tres mesos de temps i tenim un dte.
Dia de l'espectador, dias azules de RENFE...
Per distribució
Escollim acuradament el canal de distribució que volem fer servir per fer arribar el nostre producte. No trobarem certs perfums a perfumeries de barri o certes marques de roba a qualsevol botiga.
Canal exclusiu: Només fem arribar el producte per un sol canal de distribució ben seleccionat.
Canal selectiu: Escollim els canals que compleixin amb certes característiques (per exemple Mango només selecciona locals que estiguin al centre de la ciutat en les avingudes més importants).
Distribució intensiva: maximitzar la presencia en tota mena de canals: ex. cacauets que trobem tant a supermercats, màquines de vending, quioscs, bars...
Per comunicació
Escollirem el mitjà de comunicació que millor satisfaci les nostres necessitats de comunicació i a la vegada, detallarem les característiques, p.ex. televisió, a una franja horària X, premsa de menors de 25 anys, a la pàgina del mig...
Certes marques de roba fan publicitat esponsoritzant campionats de golf o de polo, concentrant-se en sectors de la població minoritaris però amb alt poder adquisitiu.
Estudi de mercat
L’anàlisi de l’entorn i l’estudi de mercat són aspectes molt importants en tot projecte empresarial. Es tracta de detallar el mercat en el qual l’empresa mantindrà l’activitat principal, així com els clients potencials i la competència. Una empresa ven productes i serveis i, per tant, necessita clients disposats a comprar-los. És per això que l’estudi de mercat ens ha de permetre analitzar qui són aquests clients; les necessitats, els desitjos, les demandes i les expectatives que poden tenir; com es comporten a l’hora de comprar i de quina manera haurem de respondre a tot això.
1. Característiques del sector
Qualsevol emprenedor ha de conèixer el sector on es desenvoluparà l’activitat, és a dir, ha de conèixer els clients, els proveïdors, la competència, l'amenaça de nous competidors, les possibles aliances (col·laboradors), les barreres d’entrada existents i els productes o serveis substitutius. També és habitual analitzar la concentració o dispersió de les empreses, l’evolució i les perspectives futures, el volum de facturació, les regulacions del sector i els permisos necessaris per actuar-hi, entre altres.
2. Anàlisi del mercat
Es tracta d’analitzar el mercat en què l’empresa desenvoluparà l’activitat i identificar les forces competitives que el configuren.
3. Àmbit, evolució i tendències
- Zones geogràfiques on es preveu comercialitzar el producte o servei (barri, municipi, comarca, entre altres). Cal diferenciar entre el mercat real, aquell que actualment compra o consumeix el producte o rep el servei, i el mercat potencial, aquell que pot comprar o consumir el producte o servei de l’empresa independentment que ja ho faci o no.
- Tendència i evolució del mercat: s’ha de conèixer si aquest mercat pateix una evolució a l’alça o a la baixa, i en quina proporció respecte a anys anteriors, o bé si efectua una desviació cap a productes o serveis semblants.
- Volum del mercat: calculat en unitats, en euros, en quilos, amb la màxima segmentació possible (en àrees geogràfiques, per canals de distribució i d’altres).
- Possibles canvis en la demanda.
- Quota de participació estimada de l’empresa: part del mercat que compra o consumeix el producte o servei de l’empresa en relació amb el total de compradors o consumidors del producte genèric.
4. Segmentació del mercat
Segmentar el mercat és agrupar els clients en grups similars en funció de les seves necessitats i dels seus hàbits, que solen estar vinculats a criteris demogràfics, geogràfics, socioeconòmics, i altres. Amb aquesta segmentació podrem establir plans específics per a cadascun d’aquests segments homogenis i pensar en les raons per les quals el producte pot satisfer-ne les necessitats.
5. Anàlisi dels clients
Es tracta d’aprofundir en el coneixement dels clients i arribar a comprendre’n el comportament. Caldrà, doncs, determinar quins seran els clients potencials de l’empresa. Aquests clients poden ser particulars (consumidors finals), dels quals hauríem de definir-ne el perfil (sexe, edat, estat civil, poder adquisitiu, nivell cultural, localització geogràfica, hàbits de consum, entre altres), però també poden ser empreses, administracions públiques o associacions, fundacions, i d’altres.
En qualsevol cas, siguin del grup que siguin, és important determinar qui són, on són, què necessiten i què demanen, i quines millores desitjarien respecte als productes que ara ofereix la competència i en què basen les seves decisions de compra.
Respondrem les següents preguntes sobre els clients:
- Qui compra? Característiques personals.
- Per què compra? Motivacions.
- Què compra? Productes i marques.
- Com compra? Busca el producte o compra el que se li ofereix.
- Quant compra? Quantitats.
- On compra? Establiments, context, distància.
6. Anàlisi de la competència
En aquest apartat s’ha d’analitzar la competència més directa, és a dir, les empreses que ofereixen els mateixos (o similars) productes o serveis i que s’adrecen al mateix públic. És important no limitar-se a fer una llista d’aquests competidors, ja que cal conèixer els aspectes més importants que els caracteritzen:
- Identificar quins competidors hi ha.
- On són, en quines zones operen i quina és la seva quota de mercat.
- A qui venen i quina és la imatge que té d’ells el client potencial? Tenen prestigi?
- Quins productes o serveis ofereixen i amb quines garanties? Són innovadors? Tenen qualitat?
- Quina és la seva política de preus, descomptes i condicions de pagament?
- Inverteixen part del seu pressupost en promoció i publicitat?
- Quina estratègia competitiva utilitzen? Quins avantatges tenen, quines són les seves mancances, i per què tenen èxit o per què no?
7. Anàlisi dels intermediaris
En alguns sectors, si l’empresa no ven directament al client, és important conèixer els intermediaris (distribuïdors, detallistes, entre altres), perquè incideixen en la qualitat i la imatge que es dóna. Cal saber qui i quants són, com treballen i com poden agregar valor a l’empresa.
8. Anàlisi dels proveïdors
Els proveïdors influeixen de manera directa en la qualitat dels productes o serveis d’una empresa. S’ha de conèixer els possibles proveïdors i identificar els que ofereixin avantatges competitius als productes o serveis que ens disposem a desenvolupar. En general, cal escollir els proveïdors que ens ofereixin una qualitat acceptable a un preu raonable, tenint en compte també els terminis de pagament i els descomptes o ràpels, però sense oblidar els terminis de lliurament, ja que poden ser crítics en alguns processos productius.
Referències
Màrqueting
- Màrqueting digital
- Tipus de màrqueting
- El màrqueting inbound
- SEO
- Calendari editorial
- Mesurament
- Estratègia outbound
- Eines
- Referències
Màrqueting digital
El màrqueting digital (o en línia) consisteix a contactar possibles clients aprofitant l'internet i tots els seus canals, entre d'altres:
-
Cercadors
-
Xarxes socials
-
E-mail
-
Portals web
A més d'utilitzar-los, podem obtenir feedback (automatitzat) per a modelar l'estratègia que hem dissenyat per a aconseguir l'objectiu que hem definit.
Procés
El procés de l'acció de màrqueting digital podria ser el següent:
Objectius ⇨ Estratègia ⇨ Mesurament ⇨ Optimització
-
Establir objectius que volem aconseguir. Caldrà definir els compradors i analitzar la competència.
-
Establir la nostra estratègia, definint els indicadors que determinaran si els objectius s'han complert**.**
-
Executar l'estratègia i mesurar les taxes de conversió (accions previstes de la nostra estratègia que s'han complert) utilitzant les eines adients.
-
Optimitzar els resultats, especialment si els resultats no han estat bons, replantejant la nostra estratègia.
Objectius
El màrqueting digital requereix un pla amb objectius ben definits. Aquests objectius han de tenir unes característiques, anomenades SMART:
-
específic: els objectius estan clarament definits i exposats, de manera que tot l’equip entén l’objectiu i per què és important
-
mesurable (analítica web)
-
assolible,
-
orientat a resultats
-
basat en un temps delimitat
Alguns possibles objectius de màrqueting: promocionar nous productes o serveis, créixer en presència digital, generar leads, dirigir-se a nous clients, retenir clients existents, construir coneixement de marca, desenvolupar lleialtat de marca, incrementar vendes i/o beneficis, expandir-se a un nou mercat, fer créixer el teu share, convertir-se en un referent d'autoritat al sector.
Tipus de màrqueting
Podem fer una primera classificació d'estratègies de màrqueting:
-
La outbound, o màrqueting tradicional, on l'objectiu és vendre en un sol sentit, sense comunicació des dels usuaris.
-
La inbound, basada en màrqueting de continguts, on l'objectiu és que l'usuari et trobi a tu en lloc d'anar a buscar-lo, en crear un canal. Això s'aconsegueix amb creació de continguts i el mesurament d'objectius.
L'estratègia outbound és la tradicional, però també té una part digital: el pagament als cercadors i anuncis als mitjans digitals. Aquesta estratègia es considera avui en dia superada si s'utilitza de forma aïllada, tot i que pot ser un complement a la inbound.
El màrqueting inbound
Ens cal arribar al nou consumidor. L'estratègia consisteix a formar una relació de valor en el temps que condueixi de forma natural a la compra, satisfent els seus desitjos i resolent els seus problemes.
El cicle de vida del màrqueting inbound seria:
-
Atreure els desconeguts perquè siguin visitants.
-
Convertir els visitants en oportunitats de venda.
-
Tancar les oportunitats i fer clients.
-
Delectar als clients i convertir-los en promotors.
Una forma més general de veure el cicle de vida de les compres és el funnel.
Funnel
El funnel o embut de conversió defineix els diferents passos que ha de donar un usuari o visitant per a convertir-se en client.
Una conversió es produeix quan el nostre client potencial executa una acció clau que hem definit en la nostra estratègia de màrqueting. Això pot convertir un visitant en un lead, o en general, fer que el client potencial progressi dins del funnel.
Les fases del funnel són:
-
TOFU (top of the funnel): és l'etapa de descobriment de la marca. L'usuari busca contingut educacional que l'ajudi a identificar, definir i comprendre el seu problema i estableix els requeriments de la solució.
-
MOFU (middle): l'usuari ja coneix el seu problema i entra en una fase de consideració en la qual busca possibles solucions. Detecta les empreses que poden oferir-li un producte / servei d'acord amb les seves necessitats i requeriments.
-
BOFU (bottom): l'usuari analitza les possibles opcions i selecciona l'empresa que millor solucioni el seu problema. És a dir, pren una decisió.
Cada fase té diferents recursos per fer progressar els nostres clients potencials.
-
TOFU: Consciència. Hem de detectar aquests problemes o necessitats, encara que no tinguem la solució. En aquesta etapa els formats més utilitzats són guies, eBooks, blog post o white papers. Tècniques: SEO, anuncis, xarxes, vídeos, linkbuilding, tràfic directe (orgànic). Volem generar leads.
-
MOFU: Consideració. Hem de proporcionar contingut valuós que ajudi en aquesta decisió. Vídeos, podcast, comparacions de productes, webinars o guies d'expert són els formats més efectius en aquesta fase. Tècniques: drip màrqueting, missatges dirigits al llarg del temps, en format correu o altres.
-
BOFU: Conversió. Hem d'oferir una solució que porti al client potencial a una futura venda. Els millors formats per a aquesta fase són casos d'estudi, demo de producte o documentació del producte o servei. Tècniques: landing pages.
Buyer persona
Per poder definir la nostra estratègia, s'utilitza el buyer persona: una representació semi-ficticia del client ideal, amb dades demogràfiques, patrons de comportament, motivacions, objectius i reptes. Aquesta construcció es basa en dades, no en suposicions.
Com podem fer-ho?
-
Recollint dades demogràfiques en enquestes en línia.
-
Analitzant el tràfic dels teus portals web, que ens ofereix informació demogràfica, paraules clau, etc.
-
Utilitzant informes i estudis oficials i d'empreses especialitzades.
Aquesta informació ajudarà a establir patrons i tendències que definiran l'estratègia de màrqueting i vendes.
Leads
Un cop tenim la nostra buyer persona, podem establir estratègies per a conduir als clients potencials cap a la compra. L'estratègia inbound requereix aconseguir leads. Un lead és un usuari que ha lliurat les seves dades a una empresa i que, com a conseqüència, passa a ser un registre de la base de dades amb el qual l'organització pot interactuar.
Es poden aconseguir leads amb diferents mètodes promocionals: un portal informatiu, landing pages (regals), un blog, xerrades, esdeveniments i fins i tot anuncis (màrqueting tradicional outbound).
Un cop tenim leads, els hem d'organitzar en una base de dades que ens permeti fer un seguiment dels clients potencials. Els leads poden acostar-se fins a ser clients. Això es pot veure reflectit al funnel.
Cercadors i paraules clau
Els cercadors han de ser amics de la nostra estratègia. Hem de trobar les paraules clau (o keywords) per al servei o producte que oferim, ja que aquestes seran les que utilitzin els nostres potencials clients.
L'objectiu és que el tràfic arribi al teu portal web de forma orgànica, és a dir, natural, mitjançant el posicionament dins dels motors de cerques (Google, principalment).
En aquest enllaç, Google explica com funciona el seu cercador. Aquesta pàgina explica que, a l'hora de fer la cerca, es tenen en compte coses com:
-
paraules de la consulta
-
La rellevància i usabilitat de les pàgines
-
El nivell de coneixements de les fonts
-
la teva ubicació i configuració
Les paraules clau han d'incloure aspectes com:
-
El nostre producte o servei
-
El nostre aventatge competitiu
-
El nostre públic objectiu
Segons la intenció de l'usuari, els keywords poden ser:
-
Informatius: l'usuari busca informació.
-
Transaccionals: l'usuari té intenció de convertir.
-
Navegacionals: l´usuari vol anar a cert lloc.
Segons el volum de cerques, els keywords poden anar dels més genèrics als més concrets:
-
Head (genèriques), amb molta competència i poca conversió.
-
Middle Tail: al mig
-
Long Tail: cerques més específiques, amb poca competència i més conversió. Les més interessants per començar.
Per fer un estudi senzill, podem començar per Google Autosuggest, Google Trends o Google Ads (Keyword Planner).
Estratègies i tècniques
-
SEO: es vol millorar l'autoritat i rellevància d'un portal mitjançant bon contingut amb paraules clau, HTML ben estructurat, contingut ben estructurat, bon temps de càrrega, bona experiència UX, bones URLs. També factors externs: qui ens enllaça i amb quina autoritat.
-
SEM: anuncis, utilitzant keywords (paraules clau) (Google Ads), Facebook Ads, vídeos, etc.
-
Màrqueting de continguts (orgànic): creació i distribució de contingut rellevant per a atreure un públic objectiu definit. Pot ser web, vídeo o xarxes socials. Per exemple: infografia, fotografia, posts de cites, vídeos, gràfics de dades, captures de pantalla, instruccions pas a pas, call to action (CTA), preguntes / questionaris, memes, gifs animats, e-books.
-
Landing pages: conversió de visitants a leads.
-
Remàrqueting: detecció de visitants que tornen (Google Ads).
-
Disseny responsive (accés multiplataforma als nostres continguts).
-
Email màrqueting.
-
Cerques locals: Google My Business, gestió d'opinions.
-
Lead scoring: es tracta de valorar numèricament la proximitat de l'usuari al client ideal. Això ajuda al procés de personalització de la comunicació.
-
Automatització de màrqueting: permet generar fluxes de treball per gestionar leads (Hotspot). També relacionat amb el lead nurturing: automatització de les interaccions amb l'usuari.
-
Màrqueting d'influencer.
-
Tests A/B.
SEO
Tenim dos tipus de SEO, o sigui, aspectes que poden afavorir la visibilitat orgànica del nostre portal web dins dels cercadors:
-
Off-page SEO: coses que no depenen directament del nostre control, especialment, backlinks que milloren el CTR (Click-Through Rate).
-
On-page SEO: inclou optimització de paraules clau, temps de càrrega, experiència d'usuari, optimització del codi i format de les URL.
També tenim una altra classificació, en funció de la valoració ètica de les tècniques que utilitzem:
-
Black Hat SEO: tècniques poc ètiques, o que contradiuen les directrius dels cercadors. És una estratègia de curt termini, arriscada i que no aporta valor. I més important: si s'utilitza, els cercadors poden penalitzar el portal, i fer-lo perdre rellevància.
-
White Hat SEO: tècniques ètiques, alineades amb les directrius dels cercadors.
Contingut
El contingut és el més rellevant per aconseguir posicionar-se, molt més que les paraules clau, les etiquetes, o els backlinks. I molt més que l'aspecte.
La qualitat del contingut depèn de diferents factors, pot ser: utilitat, educatiu, entretingut, rellevant, informatiu, original i autoritatiu (respectat). No ha de ser: promocional, publicitari, per a l'empresa, spam, fet només de paraules clau, copiat.
La qualitat determina el teu rànquing, ja que determina quines paraules clau són rellevants, i si atreu o no tràfic d'altres portals, incrementant la teva autoritat al cercador. Alguns consells:
-
El contingut s'ha de dirigir al teu client, i ho ha de fer molt llegible i entretingut. Però també als cercadors: millor utilitza paraules que imatges, vídeos o animacions.
-
No repeteixis les paraules clau de forma no natural, els cercadors ho detecten.
-
Els primers paràgrafs són molt importants. Escriu com si fos un diari: primer el més important.
-
No escriguis texts massa llargs, però tampoc massa curts, es diu que 1000 paraules per pàgina és una bona mida.
Paraules clau
Són paraules que s'inclouen en una cerca d'un usuari. Per tant, si volem que els cercadors ens trobin, han d'aparèixer al nostre portal. Hem d'investigar quines són les millors per al nostre benefici, i això es fa amb eines (habitualment de pagament), mai de forma intuïtiva.
Un cop les tenim, hem de decidir com les utilitzem:
-
Al text de ancoratge (text visible de l'enllaç).
-
En certes etiquetes HTML:
<title>, <h1>, <h2>, <h3>, <meta name="keywords">
(menys important avui en dia). -
En l'atribut ALT de les imatges
<img>
. -
En el text del portal web, de format natural, utilitzant la densitat correcta.
-
Utilitzant-les a diferents seccions del portal, en funció de quina secció hi som, i amb cua més o menys llarga (head / tail).
-
Envolta-les d'etiquetes d'èmfasi:
<em>, <strong>, <b>
, les aranyes ho detecten.
Etiquetes HTML
Són menys importants que el contingut (text), però poden ser significants:
-
Títol
<title>
: es veu a la pestanya navegador, però especialment important és que apareix a la capçalera d'una entrada de la SERP.-
El títol no hauria de ser de més de 64 caràcters, i entre 3 i 10 paraules.
-
Ha d'incloure el nom de la pàgina i paraules clau. Un format possible és "Títol: algunes paraules clau".
-
-
Capçaleres
<h1>...<h6>
: les aranyes les llegeixen per entendre com s'organitza jerarquicament el portal. -
A les imatges
<img>
, utilitza l'atribut ALT i TITLE per descriure la imatge. -
Si pot ser, utilitzar sempre etiquetes amb contingut semàntic (capçaleres), que no aquelles que només serveixen per organitzar visualment (p. ex.
<div>
). -
<meta name="description">
inclou el resum de la teva pàgina. -
<meta name="keywords">
inclou les paraules clau, tot i que els cercadors ja no solen utilitzar-ho.
Disseny i organització
Segueix alguns principis senzills:
-
La jerarquia no hauria de tenir massa nivells, facilita la feina als visitants i als cercadors.
-
La informació s'ha d'estructurar de head a tail, amb la informació més important a les pàgines més dalt de la jerarquia.
-
És important treballar amb les paraules clau per pàgines.
-
Les pàgines importants s'han de poder arribar des de la navegació de la pàgina principal.
-
No duplicar contingut: millor, tenir diferents camins per arribar al mateix contingut.
-
Les URL han de ser no massa llargues, i incloure paraules clau.
-
Testeja la navegabilitat interna de l'estructura.
Enllaços d'entrada
La quantitat i especialment la qualitat (millor rànquing) dels enllaços d'entrada (backlinks) afecta el teu rànquing. A més, si una pàgina té pocs enllaços de sortida, i està relacionat amb els continguts del teu portal, els seus enllaços compten més.
Alguns consells (White Hat):
-
El més important: crea contingut que altres portals vulguin enllaçar.
-
Si vols que t'enllacin, fes córrer la paraula: emails, xarxes socials, notes de premsa.
-
Fes peticions personals a portals de qualitat (no automatitzades) perquè t'enllacin (webmasters, bloggers, altres contactes).
-
Negocia amb altres portals de qualitat l'intercanvi d'enllaços, però evita que sigui automatitzat.
-
Utilitza related:domain per veure on es parla d'un domini.
Comprar enllaços és considerat Black Hat.
Altres tècniques
Altres tècniques a considerar:
-
Afegir el teu portal als cercadors perquè sigui indexat. A Google, mitjançant la Search Console.
-
Crear sitemaps per ajudar als cercadors a entendre l'estructura del teu portal. Veure sitemaps.org.
-
Utilitzar rich snippets (fragments enriquits) són segments de codi HTML amb contingut que expressa la seva funció dins de la web. Pots mirar-te la pàgina schema.org.
-
Integrar xarxes socials:
-
T'ajuda a dirigir tràfic cap a la teva web.
-
Algunes xarxes socials també s'indexen. Per exemple, twitter, linkedin, pinterest, facebook.
-
Proporcionen backlinks, especialment importants si es tracta d'influencers.
-
Si vols rebre atenció de les xarxes, el teu contingut ha de ser fresc.
-
Participa en les xarxes socials en què la teva activitat pugui beneficiar-se.
-
Afegeix botons de compartir a les teves pàgines.
-
Calendari editorial
Un calendari editorial és una previsió temporal de publicacions que és la forma de gestionar i controlar les publicacions al llarg de diversos mitjans per promocionar la teva marca.
La primera cosa que cal és decidir la periodicitat de cada tipus de publicació que es pot fer. Un cop decidida, el calendari es pot gestionar amb un senzill full de càlcul, que tingui per una banda les dates del calendari i per una altra els camps de cada publicació.
Per cada publicació, aquests són els camps recomanats:
-
Creador: qui escriu. Pots tenir també convidats externs.
-
Categoria: mitjà o temàtica de la publicació.
-
Estat: pots definir els teus propis. Una proposta seria: ajornat, estudi de paraules clau, començat, preparat, publicat.
-
Objectiu: quin és el propòsit de la publicació, dins del teu pla de màrqueting?
-
Títol: el títol que tindrà un cop publicat.
-
Paraules clau: quines paraules clau vols utilitzar.
-
Altres: inclou qualsevol altra informació útil per a tu o el creador.
Mesurament
El mesurament del resultat de les nostres estratègies quantificables se sol fer amb KPI (Key Performance Indicators):
-
Et permeten mesurar el rendiment d'un procés.
-
Representen un valor relacionat amb un objectiu que s'ha fixat anteriorment (recorda: SMART).
-
Normalment, s'expressen com un percentatge de consecució d'aquest objectiu.
A continuació veurem alguns exemples de KPIs en funció de certs objectius.
-
Augment de vendes: unitats o ingressos associats, especialment al llarg de campanyes o iniciatives.
-
Millora de beneficis: marges aconseguits després de treure despeses.
-
Share del mercat: hauràs de comprovar els ingressos que generes en relació als que genera el teu mercat.
-
Generació de leads: nombre, increment, cost dels leads i taxa de conversió de les visites a la web.
-
Obtenció de nous clients: especialment al començament, comptar el nombre, l'increment, el cost per client (quan ens ha costat obtenir-lo) i el percentatge de leads que es converteixen en clients.
-
Lifetime value d'un client: quan es vol mantenir el negoci, compta els clients que retornen (nombre i percentatge) i quan gasten al llarg de la seva vida.
-
La despesa individual: quan es gasta en cada compra, per cada perfil (si es té).
-
Les taxes de conversió: quan hi ha campanyes (landing pages, email links, proves gratuites, etc.), mesura la conversió, o sigui, quin percentatge que gent fa l'acció desitjada quan se li presenta.
-
Mètriques web: nombre de visites, visitants únics, pàgines per visita, taxa de rebot (abandonament després de visitar una pàgina), temps mitjà a la web.
-
Involucració a les xarxes social: nombre i increment de seguidors, nombre de comentaris, comparticions, generació de leads, opt-ins, tràfic cap a la web.
-
Rendiment SEO: rellevància de la web (segons diferents eines SEO), backlinks.
Estratègia outbound
Una estratègia outbound pot ser un complement de la teva inbound. Especialment al començament del teu negoci, ja que l'estratègia inbound és més de llarg termini.
A més, pot ser una estratègia personal per ajudar a convertir visitants que han arribat via inbound.
Aquestes són algunes claus:
-
Trucades i emails en fred. És important personalitzar la comunicació, especialment a les trucades. Cal fer primer una recerca, intentant identificar possibles objectius, potser amb eines de generació de leads.
-
Correu convencional. Pots obtenir leads de públic offline. Es pot automatitzar amb eines, i fer seguiment online. Es pot combinar amb el món digital, com estratègia complementària.
-
Anuncis a les xarxes i als cercadors. És complementari de l'estratègia de continguts. És important dirigir bé els anuncis cap a segments concrets. Especialment, si podem fer-ho per a nous lectors fora del canal inbound.
-
Fires, conferències i networking. Permeten obtenir nous leads cara a cara. Podem anar a esdeveniments existents o organitzar els nostres.
Eines
Hotspot és una de les eines de referència, però en tenim moltes:
-
Un sistema de gestió de continguts (CMS), com ara Wordpress o Squarespace.
-
Una eina de màrqueting per correu electrònic / generació de leads, com Mailchimp, Convertkit o Aweber.
-
Un creador de land pages, com ara Leadpages. Això és opcional perquè les pàgines de destinació es poden fer amb Wordpress i Squarespace.
-
Una eina d'automatització de màrqueting, com ara Autopilot. Això és opcional perquè l’automatització de màrqueting sovint ja està integrada en les eines de màrqueting per correu electrònic o en les eines de generació de leads.
-
Un calendari editorial: és el document que ens dirà què hem de publicar, quan ho hem de fer, on el publicarem i qui serà la persona que l'ha de fer.
Referències
- ¿Cómo crear un plan de Marketing Online paso a paso? (vídeo 19 min)
- Cómo hacer un plan de márqueting digital paso a paso
- Como hacer un calendario editorial
- 40 de fiebre
- InboundCycle
- Google Trends
- How me make money (Google)
- ¿Qué es el Inbound Marketing? Metodología y caso práctico
- Inbound Marketing: ¿Qué es y por qué usarlo hoy?
- Cómo hacer una auditoría de Marketing Digital
- Marketing Automation en Hubspot
- Tutorial de búsqueda de keywords
- The Content Marketer's Guide to Keyword Research
- The Ultimate Sales Funnel Guide for Small Business
- 16 Marketing KPIs You Need to Monitor in 2024
- What is Google Ads
- Guía SEO para principiantes
- ¿Quieres comprar un perro?
- Here are the outbound marketing tactics that still work in 2019
- Energía en edificios de oficinas
Estructura legal i pla econòmic
- Estructura Legal
- Pla Econòmic i Financer
- Referències
Estructura Legal
1. Definició Estructura Legal
L'estructura legal d'una empresa es conforma per diferents aspectes com són la forma jurídica que aquesta adoptarà (individual, societària o col·lectiva), els tràmits que es duran a terme per constituir-la i posar-la en marxa, i finalment les obligacions fiscals que tindrà arran de la forma jurídica escollida.
És convenient que pensis quins són els avantatges i desavantatges de les diferents formes jurídiques i quina implicació tindran en el teu model de negoci perquè la presa de decisió sigui la més adient per la teva empresa.
2. Forma jurídica
Abans d’iniciar qualsevol tràmit d’inici d’activitat, cal estudiar atentament la fórmula més convenient per crear l’empresa, a fi i efecte de determinar quina estructura s’adapta millor a les característiques pròpies del projecte que es vol desenvolupar.
Les diferents formes jurídiques són els tipus d'empresa que l'administració preveu que poden constituir-se segons el nombre de socis, el tipus de responsabilitat i el capital social aportat. De fet, la forma jurídica escollida per a l’activitat econòmica de l’empresa en determinarà en gran manera el sistema d’organització.
A l’hora de prendre una decisió és important conèixer el ventall de formes jurídiques que la llei recull, els seus requisits, els avantatges i els inconvenients de cada tipologia. Entre els aspectes que cal valorar abans d’optar per una forma jurídica podem assenyalar:
- La complexitat de la constitució i la gestió. Certes formes requereixen més burocràcia, o certes activitats requereixen certa forma. També els costos poden variar.
- El nombre de socis. Podem tenir formes amb una sola persona o amb diverses.
- Les necessitats econòmiques del projecte. Les formes poden requerir capital mínim.
- Els aspectes fiscals. Bàsicament, tenim dos grups: les formes subjectes a l'Impost sobre la Renda de les Persones Físiques (IRPF) i les sotmeses a l'impost de societats (IS).
- La responsabilitat patrimonial dels promotors. Variable segons la forma. Pot ser:
- Subsidiària: una persona assumeix la responsabilitat.
- Solidària: es pot exigir el deute a qualsevol soci.
- Mancomunada: cada soci respon segons l'aportació al capital social.
- Llibertat d'acció de l'emprenedor. Quan hi ha formes amb diversos socis, és possible que la decisió sigui en funció del capital aportat, o bé pot ser un vot per persona.
- Imatge. Les formes unipersonals donen una imatge menys sòlida.
- Accés a ajuts públics. Algunes formes tenen ajuts, tot i que no hauria de ser un criteri important.
2.1 Formes jurídiques individuals
Les formes jurídiques individuals realitzen la seva activitat amb el nom de l'empresari.
- Empresari individual: autònom.
- Comunitat de béns.
- Societat civil particular.
Les formes individuals estan gravades amb l'IRPF, un impost progressiu.
Les rendes gravades amb l'IRPF són:
- Rendiments del treball (sou).
- Rendiments del capital mobiliari (interessos de comptes, dividends).
- Rendiments per activitats econòmiques (empresaris). Per fer el càlcul de beneficis, tenim tres possibles règims:
- Estimació directa normal: per a grans empresaris (facturació > 600K).
- Estimació directa simplificada: petits empresaris (facturació < 600K).
- Estimació objectiva: per a activitats concretes, rendiments < 150K. Càlcul fix per cada mòdul segons treballadors i mida del local.
- Guanys i pèrdues patrimonials (transmissió de béns, premis).
2.2 Societats
Tenim quatre tipus principals:
- Societat de responsabilitat limitada
- Societat anònima
- Societat laboral
- Societat cooperativa
Les rendes estan gravades amb l'IS. Altres impostos:
- Impost sobre activitats econòmiques (IAE). Només aplicable a empreses amb facturació superior a 1M.
- Impost sobre el valor afegit (IVA) és aplicable a béns i serveis. Pot tenir dos règims:
- General: tenim el suportat i el repercutit, i la diferència s'ajusta amb l'agència tributària. Diferents tipus: general, reduit i superreduit (21, 10 i 4%).
- Especial: per a empreses detallistes, especialment agricultura, ramaderia i pesca.
- Impost de béns immobles (IBI)
- Impost sobre vehicles de tracció mecànica
- Impost sobre construccions, instal·lacions i obres.
- Impost sobre transmissions patrimonials i actes jurídics documentats.
3. Constitució
La constitució dota de personalitat jurídica a l'empresa, sent susceptible de drets i obligacions. A continuació, es mostren els tràmits necessaris segons la forma jurídica:
- Empresari individual:
- DNI del promotor
- Comunitat de béns i societat civil:
- DNI dels promotors
- Contracte públic o privat de constitució
- Sol·licitud del número d'identificació fiscal (NIF)
- Impost sobre transmissions patrimonials
- Societats mercantils:
- Sol·licitud de certificació negativa del nom o raó social
- Sol·licitud de qualificació per societats laborals i cooperatives
- Justificació d'aportacions dinerades o no dinerades
- Escriptura pública de constitució
- Sol·licitud del número d'identificació fiscal (NIF)
- Liquidació de l'impost de transmissions patrimonials i actes jurídics documentats
- Inscripció en el registre corresponent
4. Posada en marxa
La posada en marxa és posterior a la constitució, i cal per tal de tenir activitat. Són, principalment:
- Alta en el cens d'empresaris i professionals. Es fa mitjançant el model 036 o el 037 (simplificat), i pot incloure el règim especial de l'IVA escollit.
- Alta en l'impost sobre activitats econòmiques. No implica pagament si no se supera 1M d'euros de facturació, i llavors es gestiona al 036.
- Diligencia i legalització dels llibres obligatoris.
- Empresaris individuals
- Estimació directa normal
- Activitat industrial, comercial o de servei: llibre diari, llibre d'inventari i comptes anuals
- Activitat no mercantil: llibre de vendes i ingressos, llibre d'ingressos i despeses i llibre registre de béns d'inversió
- Estimació directa simplificada: factures numerades per data i agrupades per trimestres, llibre de registre de béns d'inversió, llibre de registre de vendes i ingressos, llibre de registre de compres i despeses
- Estimació objectiva: justificants de les operacions.
- Estimació directa normal
- Societats: llibre diari, llibre d'inventaris i comptes anuals, llibre d'actes, llibre d'accions nominatives (SAs) i llibre de registre de socis (SLs)
- Cooperatives: llibre de registre de socis, llibre de registre d'aportacions al capital, llibre d'actes, llibre d'inventari, llibre diari
- Empresaris individuals
- Inscripció de l'empresa en la Seguretat Social, sempre que calgui contractar personal (TA.6)
- Alta de l'empresari en el RETA
- Afiliació i alta dels treballadors (TA.1)
- Compra de locals: cal comprovar la qualificació urbanística de terrenys o locals, i demanar les llicéncies municipals corresponents. Si hi ha una compra, cal formalitzar-la mitjançant un contracte de compravenda (amb escriptura pública al Registre de la Propietat). També, cal donar-se d'alta a l'IBI.
- Arrendament de locals: contractes (verbals o escrits), regulats per la llei d'arrendaments urbans.
- Sol·licitud de llicència d'obres d'acord a la normativa urbanística municipal. Objecte: nova planta, reformes d'edificacions o obres menors.
- Sol·licitud de llicència d'apertura
- Comunicació d'apertura de centre de treball
5. Tramitació
Aquests són els organismes i els tràmits que gestionen:
- Ajuntaments: llicència d'obres, apertura i altres tributs
- Delegació o administració d'hisenda: alta IAE, alta cens d'etiquetes i opcions IVA, alta impost sobre béns immobles, alta estimació objectiva o directa, legalització de llibres obligatoris, obtenció del NIF
- Tresoreria General de la Seguretat Social: inscripció al règim d'autònoms, inscripció de l'empresa, afiliació i alta de treballadors
- Direcció provincial de Treball i Seguretat Social: comunicació d'apertura del centre de treball, segellar el calendari laboral
- Oficines de Treball: registre dels contractes formalitzats amb treballadors, comunicació de contractacions que no requereixen contracte
- Direcció Provincial d'Indústria: inscripció en el registre de la propietat industrial
La finestreta única empresarial (VUE) és una iniciativa que permet la creació d'empreses de forma més fàcil:
- Informa i orienta a l'emprenedor
- Facilita la tramitació a tots els organismes des d'un sol lloc físic
El procés telemàtic es pot fer dirigint-se als punts d'atenció a l'emprenedor (PUE). Tot es gestiona mitjançant un únic document, el document únic electrònic (DUE).
6. Altres aspectes
- Tingueu present les normes de seguretat i higiene en el treball i la prevenció de riscos laborals.
- S’ha de preveure la possibilitat de protegir el vostre producte, nom o marca d’alguna manera.
- Si el local no és de la vostra propietat, haureu de formalitzar un contracte de lloguer.
- Si heu escollit el sistema de franquícia, haureu de signar el contracte que us lligarà amb el franquiciador.
Pla Econòmic i Financer
1. Introducció
El pla econòmic i financer esdevé la síntesi dels aspectes econòmics del pla d’empresa, les magnituds bàsiques del qual s’obtenen a partir dels diversos apartats del pla. Per tant, per ser coherent és necessari que les dades coincideixin amb les obtingudes en cada un d’aquests apartats. Així, per exemple, de les previsions de vendes i dels preus definits se’n desprendran els ingressos previsibles de l’empresa.
Una despesa és una compra d'un bé o servei. Per exemple: matèries primeres, salaris, lloguers, manteniment i neteja, publicitat, consum, assegurances, constitució de l'empresa, etc.
Una inversió és el conjunt de béns que l'empresa adquireix, com màquines i eines, per obtenir el producte o servei, i que mai es vendran.
El pla financer, sempre que es compleixin les circumstàncies previstes en el pla, evidenciarà la viabilitat o no del projecte de negoci.
En aquest pla fóra també interessant, si es tenen els coneixements adients, realitzar un estudi de la viabilitat de la inversió del projecte a través del càlcul del valor actual net (VAN), de la taxa interna de rendibilitat (TIR) i el criteri del pay-back o termini de devolució. També resulta interessant el càlcul d’algunes ràtios com la de rendibilitat, de solvència, d’endeutament, de liquiditat i d’altres.
2. Pla d’inversions i de necessitats inicials
Representa el càlcul de la inversió inicial, així com de la forma de titularitat dels actius de l’empresa. Inclourà el desemborsament necessari per finançar tant l’immobilitzat material (local, maquinària, mobiliari i utillatge, equips informàtics i d’altres) com l’immaterial (despeses de constitució, de primer establiment, drets de traspàs i d’altres) i les existències necessàries per cobrir l’estoc inicial. També cal incloure un estoc de tresoreria per fer front a pagaments inicials, i això permetrà alleugerir les tensions de tresoreria durant els primers mesos d’activitat.
3. Pla de finançament
El finançament de la inversió inicial i de les altres necessitats podrà fer-se mitjançant finançament extern o bé mitjançant finançament propi. Aquest finançament haurà de tenir en compte el finançament del fons de maniobra necessari per al desenvolupament de l’activitat normal de l’empresa. Es tracta que el total de recursos sigui igual al total de les necessitats estimades. Opcions:
- Aportacions pròpies i de socis temporals.
- Lloguer de béns i equips (en lloc de comprar-ne). Renting i leasing.
- Préstecs.
- Ajuts públics i subvencions.
Després, podem comptar amb l'autofinançament associat a les reserves, o bé pel manteniment, com són amortitzacions i provisions. El dia a dia també el podem gestionar amb eines com el confirming, els comptes de crèdit, descobert bancari, descomptes comercials, factoring, canvis de termini a proveïdors i descomptes de pagament immediat.
4. Pla de tresoreria
Permet observar la liquiditat de l’empresa i, per tant, preveure les necessitats de tresoreria que puguin sorgir, mitjançant l’especificació de les entrades i les sortides. Es fa una anotació mes a mes de les entrades i les sortides. També cal detallar les hipòtesis considerades per al seu càlcul, considerant la possible estacionalitat de l’activitat. Això permetrà preveure a temps les mesures adients per solucionar els desequilibris de caixa que se’n puguin derivar.
Per exemple, per a les entrades podríem especificar: aportacions dels socis, préstecs, vendes, interessos dels comptes.
Per les sortides: devolució de préstecs, lloguers, compra de maquinària, assegurances, publicitat, compra de mercaderies, salaris, seguretat social, impostos, subministraments.
Per cada mes, mostrem la diferència entre entrades i sortides, per saber quants diners tindrem al compte bancari.
5. Compte de resultats
Mostra els beneficis o pèrdues esperats per l’empresa com a diferència entre ingressos i despeses. És important especificar tant les diferents partides que determinen el compte de resultats com la seva tendència i evolució, destacant com poden afectar els canvis al resultat global de l’empresa.
És interessant fer totes aquestes previsions considerant diferents escenaris (negatiu, positiu, òptim) per poder estar preparats davant de les diferents necessitats que requerirà cadascun d’aquests casos.
5.1 Amortitzacions
Una amortització és una part d'una despesa més gran, respecte a un element que ens ha de durar un cert temps. Això es fa amb les inversions, per exemple, una maquinària: si ens dura deu anys, l'amortització és la desena part del cost que comptabilitzem anualment.
5.2 Estructura del compte de resultats
A una empresa tenim els comptes d'explotació, que es relacionen amb la seva activitat econòmica habitual, i els resultats financers, relacionats amb l'activitat financera. Trobem els següents apartats:
- Ingressos d'explotació: vendes.
- Despeses d'explotació: lloguers, amortitzacions, assegurances, publicitat, compra de mercaderies, salaris, seguretat social, impostos, subministraments.
- Ingressos financers: interessos bancaris.
- Despeses financeres: interessos del préstec.
Això ens permet calcular el resultat d'explotació (o benefici abans d'interessos o impostos) i el resultat financer. Si els sumem, tenim el resultat abans d'impostos. Si li descomptem els impostos, tenim el resultat de l'exercici (o benefici net).
Si els resultats són negatius, hem de fixar-nos si els resultats d'explotació són positius o negatius. Si són positius, el problema és que encara tenim resultats financers negatius, que es considera normal especialment si estem començant.
5.3 Regles per l'elaboració
Aquestes serien els principis a seguir per a l'elaboració:
- És despesa tot allò que l'empresa compra i consumeix.
- Per a compres i vendes, apliquem el criteri de meritació: es considera despesa quan s'ha generat, encara que no s'hagi fet el pagament, i els ingressos quan es meritin, encara que no s'hagin percebut encara.
- Tots aquells diners que entren a la nostra empresa i hem de retornar no es consideraran ingressos, i viceversa: els que surten i han de tornar-nos no es consideren despesa.
El Pla General Comptable, a més d'aquests principis, identifica uns altres: empresa en funcionament, uniformitat, no compensació i importància relativa.
6. El balanç
El balanç és la representació comptable del patrimoni de l'empresa.
El patrimoni d'una empresa es compon de:
- El conjunt de béns: tot allò que pertany a l'empresa, com maquinària, locals, vehicles, mobiliari, patents, etc.
- El conjunt de drets: tot allò que se li deu a l'empresa, com factures dels seus clients, lletres de canvi per pagar, etc.
- El conjunt d'obligacions: tot allò que l'empresa deu, com els préstecs demanats al banc, lletres de canvi que es deuen a proveedors, etc.
En tot balanç es compleix sempre que actiu = passiu + patrimoni net.
6.1 Actius
Són els béns que posseeix l'empresa. Tenim la següent classificació:
- Actiu no corrent (de llarg termini)
- Immobilitzat intangible: aplicacions informàtiques, drets de traspàs, despeses d'investigació.
- Immobilitzat material: construccions, eines, maquinària.
- Inversions immobiliàries: terrenys o edificis llogats o en venda.
- Immobilitzat financer: accions a altres empreses, bons, fiances.
- Actiu corrent (curt termini: un any com a molt)
- Existències: mercaderies, materies primeres, recanvis.
- Crèdits pendents de cobrament: clients, deudors.
- Efectiu: caixa, banc.
6.2 Passius
Són tot allò que l'empresa deu. Pot ser:
- Passiu no corrent: deutes que cal retornar a llarg termini. Crèdits.
- Passiu corrent: deutes a retornar a curt termini (menys d'un any): pagament a proveïdors.
6.3 Patrimoni net
És la riquesa de l'empresa: capital, reserves, pèrdues i guanys.
7. Balanç final previsional
Al final de l'exercici, hem d'analitzar el balanç.
El passiu ens diu com financem l'empresa, i l'actiu en què invertim o gastem aquests diners. Al començament de l'exercici són iguals, i la seva diferència al final de l'exercici ens diu si hem tingut beneficis o pèrdues.
8. Impostos
8.1. Impostos
Els principals impostos que un emprenedor pot trobar-se són:
* Impostos directes: impost sobre la Renda de les persones Físiques (IRPF), Impost sobre Societats (IS)
* Impostos Indirectes (Impost sobre el Valor Afegit (IVA)
8.2. Impost sobre Societats
L'Impost sobre societats és un impost:
- Directe. Es calcula en funció de la renda obtinguda per la societat.
- Personal. S'aplica a totes les rendes obtingudes pel contribuent.
- Proporcional. S'aplica un tipus de gravamen fix amb independència de la renda.
- Periòdic.
El període impositiu és anual, coincidint amb l'exercici econòmic de cada societat. Si la societat no fixa un període diferent, per defecte, l'any fiscal va de l'1 de gener al 31 de desembre del mateix any. L'impost sobre societats es merita l'últim dia del període impositiu.
L'esquema de liquidació de l'impost parteix del resultat comptable de la societat, sobre el qual es calcula la base imposable a la qual s'aplica el gravamen corresponent. A partir d'aquí, s'hi apliquen les deduccions a les quals tingui dret, obtenint la quota diferencial.
El tipus de gravamen general és del 30%.
Per a les entitats de dimensió reduïda (amb una xifra de negoci inferior als 10 M€) que compleixen unes condicions determinades, el tipus de gravamen és del 25% per la part de la base imposable entre 0 i 300.000 € i del 30% per l'excés.
Pel que fa a les entitats amb una xifra de negoci inferior als 5 M€ i una plantilla de menys de 25 treballadors que tributen al tipus general amb condicions, el tipus de gravamen és del 20% si base imposable és inferior a 300.000 € i del 25% per la resta.
8.3. IRPF
L'impost sobre la renda de les persones físiques és un tribut de caràcter personal i directe que grava, segons els principis d'igualtat, generalitat i progressivitat, la renda de les persones físiques d'acord amb la seva naturalesa i les seves circumstàncies personals i familiars.
8.4. IVA
L’impost sobre el valor afegit (IVA) és un impost indirecte que grava el consum i que recau sobre el lliurament de béns i prestacions de serveis que fan empresaris o professionals, com també sobre les importacions i adquisicions intracomunitàries de béns.
Referències
- Elección de la forma jurídica
- Barcelona Activa Emprenedoria
- Barcelona Activa: Formes jurídiques i tràmits
- Barcelona Activa: Activitats
- Canal Empresa: Constitució i tràmits
- PAE Electrónico
- Estimación objetiva (módulos) 2020
- Simulador de préstec
Pla d'empresa
- Primer checkpoint
- Segon checkpoint
- Tercer checkpoint
- Quart checkpoint
- 1 Pla jurídic
- 2 Pla econòmic financer
- 2.1 Pla d'inversió inicial
- 2.2 Pla de finançament
- 2.3 Simulació del préstec
- 2.4 Sistema de cobrament als clients i sistema de pagament als proveïdors
- 2.5 Explicació del càlcul dels ingressos i la previsió d'ingressos d'explotació
- 2.6 Previsió de despeses d'explotació
- 2.7 Tipus d'IVA repercutit i suportat de l'activitat
- Lliurament final
Aquest és un guió per a confeccionar un pla d'empresa basat en quatre checkpoints.
Primer checkpoint
Aquest checkpoint conté dos punts: el pla estratègic i el canvas.
1 Pla estratègic
1.1 Dades personals de l'equip promotor
- Per a totes les persones implicades en el projecte:
- Nom i cognoms
- DNI
- Data de naixement
- Adreça
- Telèfon
- Formació i experiència laboral relacionada amb les diferents tasques que es desenvoluparan en la nova empresa
- Destacar els elements que estiguin més relacionats amb l’activitat empresarial: formació, experiència relacionades en atenció al públic, experiència en àmbit comercial, en gestió econòmica i administrativa, etc.
- Destacar si tens experiències prèvies en negocis similars o en el cas d’existir algun buit en relació amb algun aspecte comentar com se superaran (Punts forts i punts febles de les capacitats dels emprenedors front l’activitat)
- Aportar el currículum vitae de cadascun dels promotors a l’annex, com a carta de presentació del vostre perfil i de la vostra capacitat tècnica.
Aquesta informació es pot copiar de FOL.
1.2 Motivacions i origen de la idea
- Explicar els motius que es tenen per crear l’empresa.
- Explicar l’evolució de la idea: com va sorgir la idea de crear l’empresa? Com ha anat canviant?
- Quines persones participen en el projecte i quin és el grau d’implicació de cadascuna.
- Projecta l'empresa cap al futur: quina és la vostra visió?
- Explica els valors que voleu que es reconeguin en la vostra activitat.
- Quins criteris socials aplicareu a la vostra empresa? Veure annex: llista de criteris socials.
1.3 Descripció de l'activitat
- Quina és la missió del negoci?
- Tipus d'activitat que es durà a terme. Quin producte o servei es vendrà?
- Expliqueu la vostra proposta de valor. Elements diferencials de la vostra idea. Creativitat i originalitat.
- Petita planificació de l'activitat. Quan comença? On es fa físicament?
Entre l'apartat 1.2 i el 1.3, escriu almenys una pàgina.
2 Canvas
Inclou el canvas del model de negoci desenvolupat.
Segon checkpoint
1 Pla de màrqueting
1.1 Anàlisi de mercat
- Descripció del sector de l'activitat i projecció: descriure el sector econòmic on es desenvoluparà l’activitat empresarial de l’empresa i la previsió futura d’aquest sector de manera global.
- Concretar el mercat escollit i tendències: Definir i descriure el mercat concret al qual es dirigirà l’activitat de l’empresa (zona geogràfica, sectors, etc.), i les tendències o comportament que es preveu en aquest àmbit local.
Fer 1 a 1,5 pàgines.
1.2 Segmentació de Clients
- Determinar el perfil comú dels nostres clients potencials.
- En el cas que existeixin diferents tipus de clients, cal descriure’ls indicant quins criteris s’han utilitzat per diferenciar-los i per què s’han triat aquests criteris. Explicar si són empreses, particulars, altres entitats, administracions, etc.
- Dimensionar el mercat potencial: quants consumidors o clients componen aquests mercats.
Incloure una taula amb una fila per cada tipus de client, amb les següents columnes:
- Perfil demogràfic: gènere, edat, estat civil, mida familiar, ocupació, nivell d'estudis, nivell de renda.
- Perfil psicogràfic: estil de vida, personalitat, classe social.
- Comportament: benefici buscat, freqüència de compra, nivell de lleialtat, nivell d'ús, lloc i moment d'ús, categories d'usuaris.
Fer 1 pàgina.
1.3 Anàlisi de la competència
- Característiques de la competència (directa / indirecta): preus, localització, productes/serveis, mida, tipus de client, qualitat, directa o indirecta.
- És directa o indirecta: ofereixen el mateix que la teva empresa o un de similar, però que poden ser considerats com a competència.
- Posicionament de la competència: quina estratègia competitiva utilitza? Què fa i com per atraure clients.
- Punts forts i febles de la competència respecte de l'estàndard del mercat
Pots fer un quadre d'anàlisi de la competència directa. Per cada empresa competidora, i per la teva, afegeix aquestes columnes:
- Nom de l'empresa
- Producte o servei
- Nivell de preus
- Localització
- Punts febles respecte al mercat
- Punts forts respecte al mercat
Fer 1 pàgina.
1.4 Posicionament i avantatge competitiu
Aspectes diferenciadors del nostre producte/servei respecte de la competència directa. T'has de posar en el punt de vista del client final i explicar:
- Què ens diferencia?
- Què influirà en les decisions de compra del possible client?
- Quina necessitat es cobreix?
- Amb quina freqüència compra?
- Si l'usuari o beneficiari és diferent de qui fa la compra, indicar qui decideix la compra i quines necessitats es cobreixen.
Fer mitja pàgina.
1.5 Màrqueting-Mix del projecte
El màrqueting-mix es fa en quatre punts:
- Producte/servei: característiques i elements diferenciadors respecte de l'oferta actual (qualitat, servei postvenda, presentació, segmentació, etc.).
- Política de preus:
- Criteris per determinar-los: segons la competència, segons els costos, etc.
- Llista de preus i previsió de vendes mensual
- Forma de cobrament
- Promoció i publicitat
- Com es donarà a conèixer l'empresa i el seu producte/servei? Mitjans, promocions especials, destí de les accions.
- Cost de la publicitat i/o les accions de promoció. Cost previst, impacte previst en clients i ingressos.
- Distribució i comercialització
- Com arribarà el producte/servei al client? Canals majoristes, detallistes, punts de venda directa, etc.
- Com s'organitzarà la tasca comercial amb relació als canals.
Fer 2 pàgines.
2 Taula financera
Fer una taula esborrany anotant les despeses (fixes i variables) i els ingressos que es produeixen al teu negoci quan ja es troba en un punt d'equilibri financer. Pots assumir que això passa després d'un cert temps de funcionament, com per exemple un any.
3 Canvas (actualitzat)
Inclou l'actualització del canvas del model de negoci amb relació als aspectes desenvolupats en aquest segon checkpoint.
Tercer checkpoint
1 Pla de màrqueting en línia
1.1 Objectius
Expliqueu els vostres objectius de màrqueting. Han de ser (SMART): eSpecífic, Mesurable (analítica web), Assolible, orientat a Resultats, basat en un Temps delimitat.
1.2 Estratègia inbound
- Fes un estudi de les paraules clau del teu negoci:
- Indica les paraules clau escollides.
- Justificar-les.
- Explica on juga la teva competència.
- Defineix i crea el teu contingut SEO.
- Fes un diagrama amb la jerarquia, el títol i les paraules clau per a cada pàgina.
- Fes un esbós del text de la pàgina principal, on hi ha d’haver les paraules clau.
- Escriu el teu calendari editorial: un full de càlcul o taula amb les publicacions que preveus fer els tres primers mesos, aproximadament.
1.3 Estratègia outbound
Defineix una estratègia de màrqueting outbound per al teu negoci. Indica la teva inversió a cercadors, xarxes socials o altres mitjans.
1.4 Mesurament
Defineix almenys cinc indicadors KPI en relació als teus objectius i explica com faràs el seu mesurament.
Total: 3 pàgines (1/2, 1 i 1/2, 1/2 i 1/2).
2 Pla operacional
L’objectiu és detallar com es fabricarà el producte o quin és el procés de prestació del servei. Cal que descriguis totes les depeses que faràs.
- Recursos infraestructurals: descripció del local, ubicació, entorn, què cal fer per adequar-lo i les despeses necessàries de lloguer.
- Recursos materials: maquinària, eines, mobiliari, matèries primeres.
- Recursos humans: funcions i tasques necessàries, descriure els perfils professionals, dedicació horària, procediment de selecció i costos salarials.
- Proveïdors:
- Llista de proveïdors i forma de pagament
- Contractacions externes
- Serveis i subministraments: despeses de gestor, subministraments, comunicacions, manteniment, reparacions, transport, dietes, assegurança de responsabilitat civil, vehicles.
De 1 ½ a 2 pàgines.
3 DAFO
L'anàlisi DAFO ens ajuda a dissenyar la nostra estratègia mitjançant l'avaluació de quatre aspectes, classificables en interns/externs i positius/negatius:
- Amb origen intern, tenim fortaleses (positives) i debilitats (negatives).
- Amb origen extern, tenim oportunitats (positives) i amenaces (negatives).
Fortaleses: visuals i mostrats o ocults .. Què et fa diferent? Què destaca?
- De què ets bo de forma natural?
- Quines habilitats heu treballat per desenvolupar?
- Quins són els vostres talents o els regals naturals?
- Què tan forta és la vostra xarxa de connexions?
- Què veuen les altres persones com a punts forts?
- Quins valors i ètica us distingeixen dels companys?
Debilitats: coses a millorar. Obstacles a la vostra vida / carrera professional.
- Hàbits i trets negatius de treball.
- Educació / formació que necessita millorar.
- Què consideren els altres com a debilitats?
- On es pot millorar?
- Què tens por de fer o probablement evites? Què t’espanta? Falta confiança per fer-ho?
Oportunitats: factors externs per aprofitar per dur a terme els vostres somnis.
- Estat de l’economia.
- Noves tecnologies o temes a tractar.
- Demanda de l'habilitat o el tret que posseeix.
- Hi ha la necessitat que ningú compleixi la seva carrera professional / lloc de treball / indústria?
- Esteu en una indústria en creixement?
- Podeu assistir a xarxa, classes o conferències?
- Algun dels seus punts forts us ofereix una oportunitat?
- Alguna de les seves debilitats es convertiria en una oportunitat si es supera?
Amenaces: factors externs que poden fer mal o disminuir les possibilitats d’assolir els vostres objectius.
- Alguna de les seves debilitats inhibeixen la capacitat de créixer / augmentar / millorar (company / lfe)?
- La vostra indústria contracta o canvia / canvia d’orientació?
- Hi ha una forta competència per als llocs de treball que més s’adapten?
- Quin és el perill extern més gran dels vostres objectius?
- Hi ha noves normes professionals que no pugui complir?
- Hi ha nous requisits tecnològics / educatius o de certificació que impedeixin el vostre progrés?
Relaciona els punts forts amb les oportunitats per saber on centrar-se i prendre mesures.
Compondre amb debilitats i amenaces et mostra on treballar i millorar.
Cerqueu qualsevol manera de convertir les vostres debilitats o amenaces en oportunitats o punts forts.
1 pàgina.
4 Canvas (actualitzat)
5 Taula financera (actualitzada)
Al checkpoint anterior vas descriure les despeses. Descriu ara la previsió de vendes anuals dels tres primers exercicis. Quina seria la previsió de vendes mensual i diària per al primer exercici? (fes la divisió)
Quart checkpoint
1 Pla jurídic
Prendre com a referència el pla d'empresa Vapor Llonch.
Fer 2 pàgines.
1.1 Forma jurídica
Indicar la forma jurídica escollida i motius de l'elecció.
1.2 Tràmits
Elaborar una llista dels tràmits i els costos pertinents.
1.3 Obligacions fiscals
Descriure les obligacions pròpies de la forma jurídica escollida.
1.4 Obligacions comptables
Descriure les obligacions laborals que tindrà l'empresa, així com el règim de la seguretat social al que s'acolliran els socis i la contractació prevista, i així mateix, calcular el cost laboral.
1.5 Seguretat social
Altres aspectes a destacar en relació amb l'activitat: prevenció de riscos laborals, qualitat, medi ambient, etc.
2 Pla econòmic financer
Prendre com a referència el pla econòmic i financer autònom (punt 3).
Fer 2 pàgines i 1/2.
2.1 Pla d'inversió inicial
Detalla al màxim l'explicació de la inversió per a dur a terme el projecte, a més d'omplir el requadre de la plantilla.
2.2 Pla de finançament
Detallar al màxim l’explicació del pla de finançament (% recursos propis, aportació dels socis, nombre de socis, endeutament dels socis, condicions del préstec, etc.). Omple el requadre de la plantilla.
2.3 Simulació del préstec
Utilitza un simulador per omplir el préstec de la plantilla. A internet trobaràs diferents simuladors.
2.4 Sistema de cobrament als clients i sistema de pagament als proveïdors
Detallar el sistema de cobrament dels clients i pagament als proveïdors (contat o diferit en el temps).
2.5 Explicació del càlcul dels ingressos i la previsió d'ingressos d'explotació
Molt important, a més de quantificar els ingressos, és detallar com s’ha realitzat el càlcul dels ingressos. Utilitza la taula de la plantilla.
2.6 Previsió de despeses d'explotació
Quantifica les despeses d'explotació i calcula el resultat d'explotació. Utilitza la taula de la plantilla.
2.7 Tipus d'IVA repercutit i suportat de l'activitat
Explicació del tipus d’IVA suportat i repercutit, així com del sistema tributari de l’IVA (estimació directa, indirecta, etc.).
Lliurament final
1 Els quatre checkpoints revisats
Cal incloure tots els comentaris fets pel professor durant els quatre checkpoints, i totes les actualitzacions que calguin en funció del desenvolupament del pla.
2 Conclusions
La conclusió és l'apartat on fareu una valoració del procés de planificació d'aquesta primera idea de negoci que vàreu plantejar a l'inici. També es poden remarcar quins entrebancs heu trobat durant l'elaboració del pla i com els heu donat solució.
És aquí on senyalareu els canvis que ha experimentat la idea inicial i quins són els punts remarcables del pla. Podeu fer una projecció de futur del vostre model de negoci i de quins són els objectius que voldríeu assolir. Cal finalment agrair atots aquells que han fet possible que la vostra tasca arribés a bon port.
3 Maquetació
Tapa, contratapa, índex i paginació.