Principis
- Programari
- Execució del codi
- Expressions
- Codi
- Dades
- Memòria
- Funcions
- Recursivitat
- Objectes
- Entrada/Sortida (E/S)
- Gestió d’errors
- Paradigmes
- Concurrència
- Proves (testing)
- Algorismes
- Tècniques per a la solució de problemes
- Referències
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.
Execució del codi
El codi font escrit en un llenguatge de programació s’ha de traduir a instruccions que la màquina pugui executar. Hi ha diverses estratègies:
- Compilació: el codi font es tradueix completament a codi màquina abans de l’execució. El resultat és un binari executable. Exemples: C, C++, Rust, Go.
- Interpretació: el codi font es llegeix i s’executa línia a línia per un programa anomenat intèrpret. Exemples: Python, Ruby.
- Bytecode i màquina virtual: el codi es compila a un format intermedi (bytecode) que s’executa sobre una màquina virtual. Exemples: Java (JVM), Python (.pyc), C# (CLR).
- Compilació just-in-time (JIT): durant l’execució, el bytecode o codi interpretat es compila a codi màquina per millorar el rendiment. Exemples: JVM, V8 (JavaScript).
En la pràctica, molts llenguatges combinen diverses estratègies.
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
- a AND True = a
- 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 Morgan:
- 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
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
Mòduls
Quan un programa creix, el codi s’organitza en mòduls (també anomenats paquets o namespaces, segons el llenguatge). Un mòdul agrupa codi relacionat i permet:
- Separar responsabilitats en fitxers o directoris diferents.
- Controlar la visibilitat: decidir què és públic (accessible des d’altres mòduls) i què és privat.
- Reutilitzar codi mitjançant imports.
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.
Algunes estructures de dades habituals:
- Array: col·lecció de mida fixa amb accés directe per índex.
- Llista: col·lecció de mida dinàmica. Pot ser una llista enllaçada (cada element apunta al següent) o un array dinàmic.
- Mapa (diccionari): col·lecció de parells clau-valor amb accés directe per clau.
- Conjunt (set): col·lecció d’elements únics, sense ordre ni duplicats.
- Pila (stack): col·lecció LIFO (Last In, First Out). L’últim element afegit és el primer en sortir.
- Cua (queue): col·lecció FIFO (First In, First Out). El primer element afegit és el primer en sortir.
Operacions per a una dada:
- Reservar espai en memòria. En llenguatges orientats a objecte, crear una dada a partir d’una classe s’anomena instanciar.
- Assignar el seu valor (instrucció d’assignació).
- Obtenir el seu valor.
- Eliminar la dada, alliberant l’espai. Pot ser manual (com en C/C++) o automàtic mitjançant un Garbage Collector (com en Java o Python).
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. La mutabilitat depèn del llenguatge: en alguns, els primitius són immutables per definició; en altres, qualsevol dada es pot modificar directament en memòria.
Sistema de tipus
Els llenguatges de programació es diferencien pel seu sistema de tipus:
- Tipatge estàtic vs dinàmic: en un sistema estàtic, el tipus de cada variable es coneix en temps de compilació (Java, Rust, Go). En un dinàmic, el tipus es determina en temps d’execució (Python, JavaScript).
- Tipatge fort vs feble: un sistema fort impedeix o restringeix les conversions implícites entre tipus (Python, Rust). Un de feble permet més conversions automàtiques, cosa que pot provocar comportaments inesperats (JavaScript, C).
Alguns llenguatges amb tipatge dinàmic permeten afegir anotacions de tipus opcionals per a millorar la claredat i permetre anàlisi estàtica (per exemple, Type Hints a Python o TypeScript sobre JavaScript).
Àmbit (scope)
L’àmbit d’una variable determina on és accessible dins del codi:
- Local: la variable només existeix dins del bloc o funció on s’ha creat.
- Global: la variable és accessible des de qualsevol punt del programa.
La majoria de llenguatges utilitzen àmbit lèxic (o estàtic): l’àmbit es determina per l’estructura del codi font. Una funció definida dins d’una altra pot accedir a les variables de la funció exterior; aquest mecanisme s’anomena closure.
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 les seves variables locals, com ara paràmetres i altres dades de l’àmbit.
El heap s’utilitza per a guardar dades que necessiten sobreviure l’àmbit on es creen, com ara les estructures accessibles mitjançant referències. Les dades que només s’utilitzen dins d’un àmbit concret, com variables locals, es poden guardar al stack i s’alliberen automàticament amb el return. La decisió de què va al stack i què al heap depèn del llenguatge i del compilador.
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.
Pas de paràmetres
Els paràmetres es poden passar a una funció de diferents maneres:
- Per valor: es copia el valor de la dada. Modificar el paràmetre dins la funció no afecta l’original.
- Per referència: es passa l’adreça de memòria de la dada. Modificar el paràmetre dins la funció modifica l’original.
El mecanisme depèn del llenguatge. Alguns, com Java o Python, passen sempre per valor, però quan el valor és una referència, l’efecte pràctic és similar a passar per referència.
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. Cal tenir en compte que cada crida recursiva afegeix un frame al stack, de manera que un nombre excessiu de crides pot provocar un desbordament de pila (stack overflow). Alguns llenguatges optimitzen la recursivitat de cua (tail-call optimization) per evitar-ho.
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.
Entrada/Sortida (E/S)
L’entrada/sortida (E/S o I/O) és la comunicació entre un programa i el món exterior. Les operacions d’E/S són sempre efectes secundaris. Exemples comuns:
- Consola: llegir entrada de l’usuari i escriure text per pantalla.
- Fitxers: llegir i escriure dades al sistema de fitxers.
- Xarxa: comunicar-se amb altres sistemes mitjançant protocols (HTTP, TCP, etc.).
- Bases de dades: llegir i escriure dades persistents.
Les operacions d’E/S són habitualment molt més lentes que les operacions en memòria. Per això, sovint es gestionen de forma asíncrona o amb mecanismes de buffering.
Gestió d’errors
Els programes han de gestionar situacions inesperades. Hi ha diverses estratègies:
- Excepcions (try/catch): el flux d’execució es trenca quan es llença una excepció, i es busca un gestor (catch) que la pugui tractar. Utilitzat per Java, Python, JavaScript.
- Valors d’error: la funció retorna un valor que indica si l’operació ha tingut èxit o ha fallat. El codi que crida la funció ha de comprovar el resultat. Utilitzat per Go (
error) i Rust (Result). - Codis d’error: similar als valors d’error, però retornant un codi numèric. Habitual en C.
Les excepcions són còmodes però poden amagar el flux d’errors. Els valors d’error fan el tractament d’errors explícit, però afegeixen verbositat al codi.
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 que els defineixen i altres que també suporten:
| Llenguatge | Paradigmes principals | També suporta |
|---|---|---|
| C | procedural | multi-fil |
| C++ | procedural, orientat a objecte | funcional, multi-fil |
| Java | procedural, orientat a objecte, multi-fil | funcional, asíncrona |
| Python | procedural, orientat a objecte | funcional, asíncrona |
| JavaScript | procedural, funcional, asíncrona, basat en esdeveniments | orientat a objecte |
| C# | procedural, orientat a objecte, multi-fil, asíncrona | funcional, basat en esdeveniments |
| Kotlin | procedural, orientat a objecte, funcional, asíncrona | reactiu |
| Dart | procedural, orientat a objecte, asíncrona | funcional, basat en esdeveniments |
| Swift | procedural, orientat a objecte, funcional | asíncrona, multi-fil, basat en esdeveniments |
| Go | procedural, multi-fil | |
| Rust | procedural, funcional, multi-fil | asíncrona |
| Scala | orientat a objecte, funcional | procedural, asíncrona, reactiu |
| Ruby | procedural, orientat a objecte | funcional |
| Lua | procedural, basat en esdeveniments | orientat a objecte, funcional |
| PHP | procedural, orientat a objecte | funcional |
| Elixir | funcional, basat en missatges, multi-fil | |
| Clojure | funcional, orientat a dades | |
| Haskell | funcional, declaratiu | |
| Prolog | declaratiu | |
| SQL | declaratiu |
Alguns paradigmes de la columna “També suporta” poden venir de biblioteques o frameworks associats al llenguatge, més que del disseny del llenguatge en si. Per exemple, multi-fil en C (pthreads, C11), o basat en esdeveniments en Dart (Flutter), Swift (UIKit/SwiftUI) i C# (ASP.NET/WPF).
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. Les dades immutables són inherentment thread-safe, o sigui, no impliquen conflictes quan s’accedeixen per més d’un fil alhora.
Proves (testing)
Les proves verifiquen que el codi es comporta com s’espera. Tipus habituals:
- Proves unitàries: verifiquen el comportament d’una unitat de codi aïllada (una funció, un mètode). Són ràpides i específiques.
- Proves d’integració: verifiquen que diversos components funcionen correctament junts.
- Proves de sistema (end-to-end): verifiquen el comportament del sistema complet, simulant l’ús real.
Una asserció (assertion) és una comprovació dins d’una prova que valida una condició esperada. Si la condició és falsa, la prova falla.
El desenvolupament guiat per proves (TDD, Test-Driven Development) és una pràctica on primer s’escriuen les proves i després el codi que les fa passar.
Algorismes
Un algorisme és una seqüència finita de passos ben definits per a resoldre un problema. Alguns algorismes fonamentals:
- Cerca lineal: recórrer tots els elements d’una col·lecció fins a trobar el desitjat.
- Cerca binària: sobre una col·lecció ordenada, descartar la meitat dels elements a cada pas comparant amb l’element del mig.
- Ordenació: reorganitzar els elements d’una col·lecció segons un criteri. Exemples: bubble sort, merge sort, quicksort.
Complexitat (Big O)
La notació Big O descriu com creix el cost d’un algorisme en funció de la mida de l’entrada (n):
- O(1): constant. El cost no depèn de la mida. Exemple: accedir a un element d’un array per índex.
- O(log n): logarítmic. El cost creix molt lentament. Exemple: cerca binària.
- O(n): lineal. El cost creix proporcionalment a la mida. Exemple: cerca lineal.
- O(n log n): lineal-logarítmic. Exemple: merge sort, quicksort (cas mitjà).
- O(n²): quadràtic. El cost creix ràpidament. Exemple: bubble sort.
La complexitat es pot mesurar en temps (operacions) o en espai (memòria utilitzada).
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ó)