Laborator 05 - Git și refactorizare

Obiectiv

În prima secțiune scopul este familiarizarea cu mecanisme de versionare a codului în special Git/GitHub. În a doua secțiunea se prezintă bune practici pentru a menține codul curat.

Version Control

Un sistem pentru controlul versiuni este un sistem care înregistrează modificările suferite de un fișier sau un grup de fișiere în decursul timpului pentru a facilita revenirea la o versiune specifică ulterior.

Tipuri de sisteme pentru controlul versiunii

Exista 3 tipuri de sisteme pentru controlul versiunii:

Sisteme locale pentru controlul versiunii

Cele mai vechi VCS-uri (VCS – Version Control System) au presupus că toți dezvoltatorii lucrau în propriile directoare pe un singur sistem de fișiere partajat. Repository-ul a fost păstrat într-un director separat (adesea ascuns). A face check out si a trimite commit-uri se rezolva prin operații obișnuite cu fișiere, precum copieri și redenumiri.

Sisteme centralizate pentru controlul versiunii

Sistemele locale pentru controlul versiunii au oferit o soluție pentru gestiunea versiunii pentru aplicații dezvoltate de o singură persoană pe un anume sistem. Din păcate această soluție nu vine în ajutorul persoanelor ce trebuie să colaboreze cu alți dezvoltatori și să interacționeze cu alte sisteme. Pentru a rezolva și această problemă sistemele centralizate pentru controlul versiunii (CVCS – Centralized Version Control Systems) au fost dezvoltate.

Sistemele centralizate pentru controlul versiunii (CVCS – Centralized Version Control Systems) (precum CVS, Subversion, Perforce etc) au repository-ul pe un singur server, conectat la retea. Pentru a face check out, fisierele trebuiesc transferate din server în directorul de lucru.

Aceasta soluție oferă numeroase avantaje comparativ cu sistemele locale pentru controlul versiunii: toată lumea știe până la un anumit nivel ce fac restul colaboratorilor în cadrul proiectului; administratorii au la dispoziție o manieră eficientă de a gestiona accesul la resurse și este mult mai ușor de gestionat o bază de date centrală decât una locală pentru fiecare utilizator.

O problemă evidentă este legată de disponibilitatea nodului central: dacă acesta întâmpină orice fel de problemă o să împiedice orice acțiune din partea utilizatorilor până problema va fi rezolvată. În plus fiind singurul sistem care conține toate stările fișierelor din cadrul unui proiect, în cazul în care aceste date sunt pierdute recuperarea acestora este de foarte multe ori imposibilă.

Sisteme distribuite pentru controlul versiunii

Dezavantajele și riscurile create de un sistem centralizat pentru controlul versiunii a condus la apariția sistemelor distribuite pentru controlul versiunii (Distributed Version Control Systems – DVCS)(precum Git, Mercurial, Bazaar, Darcs etc), în cadrul acestora utilizatorii nu obțin doar ultima versiune a fișierelor, ci propria copie a repository-ului cu toate versiunile noi și vechi ale proiectului. Diferitele repository-uri se sincronizează între ele periodic pe rețea.

Un avantaj este că această abordare adaugă robustețe. În modelele locale și centralizate, un repository corupt sau un hard disk eșuat poate duce la pierderea tuturor datelor. În modelul distribuit, vă puteți recupera adesea din astfel de accidentări, deoarece mai mulți dezvoltatori își pot uni repository-le pentru a recupera întregul istoric al proiectului. Majoritatea echipelor care lucrează cu VCS distribuite vor desemna în continuare un repository ca origine centrală a copiilor lor, pur și simplu pentru că acest lucru simplifică comunicarea în echipă.

Avantajele sistemelor de versionare

Care sunt avantajele folosirii unui sistem de versionare? este salvat istoricul tuturor modificărilor, astfel că se poate reveni oricând la o versiune mai veche dacă se descoperă prin folosirea unui serviciu de hosting, codul sursă are mereu o copie de siguranță online cea mai recentă versiune a codului sursă este mereu disponibilă tuturor dezvoltatorilor, făcând astfel colaborarea și sincronizarea mult mai ușoară decât în cazul trimiterii de fișiere conținând cod sursă dezvoltatorilor interesați de proiect.

GitHub

GitHub este o platformă online, pe care dezvoltatorii o pot folosi pentru a stoca și versiona codul lor sursă. Git este un sistem de management și versionare a codului sursă care permite lucrul eficient la un proiect software. Astfel, Git este utilitarul folosit în terminal, iar GitHub este serverul și aplicația web pe care rulează acesta, locul în care păstrăm repository-ul remote.

Primii pasi în git

Instrucțiunile de instalare git se regăsesc la link-ul https://git-scm.com/downloads.

  1. Crearea unui cont pe https://github.com
  2. Crearea unui token de autentificare
    1. Autentificați-vă pe GitHub.
    2. Mergeți aici pentru a genera un token de autentificare pentru GitHub.
    3. Apăsați pe Generate new token și introduceți parola voastră de GitHub.
    4. La câmpul Note scrieți un nume pentru token, ca să știți pentru ce îl puteți folosi ulterior.
    5. La câmpul Expiration selectați No expiration.
    6. La câmpul Select scopes bifați repo Full control of private repositories.
    7. Veți vedea o căsuță verde cu un șir de caractere, salvați-l. Acesta va fi folosit în loc de parolă atunci când vi se va cere parola în linia de comandă.
  3. Pregătirea inițială a mediului Git
    student@ic:~$ git config --global user.name "Prenume Nume"
    student@ic:~$ git config --global user.email "adresa_de_email@example.com"
    student@ic:~$ git config --list
  4. Crearea unui repository gol pe GitHub
    • Apăsăm pe săgeata din meniul din dreapta sus, la profil.
    • Apăsăm pe Your profile pentru a merge pe profilul nostru.
    • Apăsăm pe Repositories și apasam pe New.
    • Pentru acest tutorial vom crea un repository privat.
    • Numele repository-ului va fi refactoring-lab.
    • Apăsăm pe Create repository.
  5. Crearea unui repository gol local
    student@ic:~$ mkdir refactoring-lab
    student@ic:~$ cd refactoring-lab
    student@ic:~$ git init
  6. Conectarea celor două repository-uri
    student@ic:~/refactoring-lab$ git remote add origin https://github.com/{username}/refactoring-lab.git
  7. Primul fișier adaugat
    student@ic:~/refactoring-lab$ echo "# Refactoring Lab" >> README.md
    student@ic:~/refactoring-lab$ git status
    student@ic:~/refactoring-lab$ git add README.md
    student@ic:~/refactoring-lab$ git status
    student@ic:~/refactoring-lab$ git commit -m "Add README file"
    student@ic:~/refactoring-lab$ git status
    student@ic:~/refactoring-lab$ git branch -M main
    student@ic:~/refactoring-lab$ git push -u origin main

Refactoring

Clean Coding

Clean Coding este un set de principii și practici folosite de către programatori pentru a scrie un cod lizibil, de încredere și ușor de intretinut. Prin urmarirea acestor principii, codul va fi mai ușor de citit și îmbunătățit și de către alți dezvoltatori, nu doar de autor. Un cod bun este:

  • Lizibil
  • De incredere
    • A fost testat?
      • A trecut testele?
      • Cat de bine a fost testat?
    • Are code smell? Code smell este o secțiune din cod care poate avea un comportament neașteptat sau chiar periculos.
  • Usor de intretinut
    • Este codul atat de strans cuplat încât o schimbare într-o parte afectează alte părți?
    • Exista cod duplicat ce necesita ca o schimbare sa fie aplicata simultan in mai multe locuri?

Principii

Un cod bun are în spate următoarele principii:

  • Trebuie sa treaca toate testele
  • Codul trebuie sa fie expresiv
  • Keep it small, keep it simple

Un principiu de design important care reflectă toate aceste principii este SOLID din OOP, care este enunțat în “Design Principles and Design Patterns” de Robert C. Martin:

  1. Single Responsibility - “There should never be more than one reason for a class to change.”
  2. Open-Closed Principle - “Software entities … should be open for extension, but closed for modification.”
  3. Liskov Substitution - “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”
  4. Interface Segregation - “Many client-specific interfaces are better than one general-purpose interface.”
  5. Dependency Inversion - “Depend upon abstractions, [not] concretions.”

Refactor

Refactoring este procesul de a face schimbări în cod care-l fac mai expresiv dar nu-i schimbă comportamentul. Exemple de refactoring:

  • redenumirea entităților
  • inlocuirea literal constants prin named constant variables
  • formatarea codului
  • încapsularea câmpurilor publice ale claselor
  • divizarea codului în funcții

Exemple:

  • ItemMeaningful names
      int nCstCnt; // number of customers -> int numberOfCustomers;
 

Numele de variabile trebuie să exprime intenția noastră, să fie pronuntabil, să aibă distingeri clare. Te poți raporta la următoarea regulă: clasele, variabilele și constantele să fie subiecte, iar metodele și funcțiile să fie verbe.

Care este diferența dintre o clasă cu numele ‘Book’ și ‘BookData’? Ambele conțin informații despre obiectul Book.

  • Limit your Scope

Declarați variabilele într-un scop cât mai limitat posibil: Un exemplu de așa nu:

Click to display

Click to display

String firstName;
String lastName;
int pos = fullName.find(','); 
if (pos >= 0) {
	lastName = fullName.substr(0, pos);
	firstName = fullName.substr(pos+1);
	println ("Hello " + firstName + ' ' + lastName);
} else {
	println ("Hello " + fullName);
}    

Scope-ul variabilelor firstName și lastName continuă existe pană la sfârșitul blocului de cod }

O soluție posibilă ar putea sa fie:

Click to display

Click to display

int pos = fullName.find(','); 
if (pos >= 0) {
	String lastName = fullName.substr(0, pos);
	String firstName = fullName.substr(pos+1);
		println ("Hello " + firstName + ' ' + lastName);
	} else {
		println ("Hello " + fullName);
	} 
  • Functions should do One Thing

Nu este recomandată următoarea practică:

Click to display

Click to display

public void addBookToCollection (Book book)
{
	List<Author> authors = book.getAuthors();
	Title title = book.getTitle();
	List<SubjectKeyword> subjects = new List<>();
		try {
			subjects = book.getSubjects();
		} catch (SubjectsUnassigned ex) {
			requestReviewOfSubjects(book);
		}   
		try {
			DecimalClassification dewey = oclc.getClassificationCode(book.getISBN());
			ShelfLocation intendedLocation = getShelfFor(dewey);
			book.setLocation (intendedLocation);
		} catch (OCLCServiceUnavailable ex) {
			log.error("Error contacting OCLC about " + book.toString(), ex);
		}   
		try {
			for (Author author: authors) {
				authorIndex.add(author, book);
		}
			titleIndex.add (title, book);
			for (SubjectKeyword subject: subjects) {
				subjectIndex.add(subject, book);
		}
			 } catch (CatalogingError ex) {
				  log.error ("Error cataloging " + book.toString(), ex); 
				 }
}

O soluție posibilă ar putea fi:

Click to display

Click to display

	public void addBookToCollection (Book book)
	{
	  addBookToCatalogs(book);
	  moveBookToIntendedLocation(book);
	}
 
	private void addBookToCatalogs(book) {
		addBookToAuthorCatalog(book);
		addBookToTitleCatalog(book);
		addBookToSubjectCatalog(book);
	}
 
	private void addBookToAuthorCatalog(Book book) {
		List<Author> authors = book.getAuthors();
		try {
			for (Author author: authors) {
				authorIndex.add(author, book);
			}
		 } catch (CatalogingError ex) {
			  log.error ("Error cataloging " + book.toString(), ex); 
		 }
	}
 
	private void addBookToTitleCatalog(Book book) {
		Title title = book.getTitle();
		try {
			titleIndex.add (title, book);
		 } catch (CatalogingError ex) {
			  log.error ("Error cataloging " + book.toString(), ex); 
		 }
	}
 
	private void addBookToSubjectCatalog (Book book) {
		try {
			List<SubjectKeyword> subjects = book.getSubjects();
			for (SubjectKeyword subject: subjects) {
				subjectIndex.add(subject, book);
			}
		 } catch (CatalogingError ex) {
			  log.error ("Error cataloging " + book.toString(), ex); 
		 }
		 } catch (SubjectsUnassigned ex) {
			requestReviewOfSubjects(book);
		 }   
		}
 
 
		private void moveBookToIntendedLocation (Book book) {
			try {
				ShelfLocation intendedLocation = getIntendedLocation(book);
				book.setLocation (intendedLocation);
			} catch (OCLCServiceUnavailable ex) {
				log.error("Error contacting OCLC about " + book.toString(), ex);
			}   
		}
 
		private ShelfLocation getIntendedLocation (Book book) 
			  throws OCLCServiceUnavailable 
		{
			DecimalClassification dewey = oclc.getClassificationCode(book.getISBN());
			return getShelfFor(dewey);
		}
  • Evitarea duplicarii

Nu este recomandată următoarea practică

Click to display

Click to display

		private void addBookToCatalogs(book) {
			attemptToAddBookToAuthorCatalog(book);
			attemptToAddBookToTitleCatalog(book);
			attemptToAddBookToSubjectCatalog(book);
		}
 
		private void attemptToAddBookToAuthorCatalog(Book book) {
			try {
				addBookToAuthorCatalog(book);
			 } catch (CatalogingError ex) {
				  log.error ("Error cataloging " + book.toString(), ex); 
			 }
		}
 
		private void attemptToAddBookToTitleCatalog(Book book) {
			try {
				addBookToTitleCatalog (book);
			 } catch (CatalogingError ex) {
				  log.error ("Error cataloging " + book.toString(), ex); 
			 }
		}
 
		private void attemptToAddBookToSubjectCatalog(Book book) {
			try {
				addBookToSubjectCatalog (book);
			 } catch (CatalogingError ex) {
				  log.error ("Error cataloging " + book.toString(), ex); 
			 }
		}

O soluție posibilă ar putea fi:

Click to display

Click to display

		private void attemptToAddBookToCatalogs(book) {
				try {
				addBookToCatalogs(book);
			 } catch (CatalogingError ex) {
				log.error ("Error cataloging " + book.toString(), ex);
			 }
		}
 
		private void addBookToCatalogs(book) {
			addBookToAuthorCatalog(book);
			addBookToTitleCatalog(book);
			addBookToSubjectCatalog(book);
		}
  • Legea lui Demeter

The law of Demeter: a method f of a class C should only call the methods of:

  • C
  • an object created by f
  • an object passed as an argument to f
  • an object held in a (data structure of a) data member of C
  • Error Handling
  • e mai bine sa folosesti try/catch si exceptii decat sa returnezi NULL sau codui de erori specifice.

Exercitii

Descarcati arhiva de aicigapsflat.zip.

  1. Deschideți proiectul și încercați să rulați cu argumentul testData/test000.dat
  2. Refactoring: Project Layout
    • Fiecare proiect Java trebuie să conțină un director src în care sunt prezente fișierele sursă .java. Creați directorul src/main/java și adăugați sursele în acesta.
    • Creați directorul src/test/data în care adaugați fisierele de test
    • Marcati directorul src ca unicul director sursa din File → Project Structure… → Modules
  3. Refactoring : Improving the Source Code
    • Deschideți Highway.java și cautați unde este apelată funcția subtract
    • Pe funcție dați click dreapta → Go To → Implementation(s) (scurtătura CTRL + click stanga) pentru a ajunge la implementarea funcției
    • Redenumiți funcția din “substract” în “remove”.
    • Conform principiilor de Clean Coding, noua functie “remove” are prea multe conditii. Selectați al doilea if din while și dați click dreapta→Refactor→Extract Method și numiți funcția replaceIfOverlapping.
    • Același lucru îl vom face cu body-ul primului if din noua funcție replaceIfOverlapping și metoda o vom numi replaceOverlappingInterval
    • Cum am făcut mai sus vom face și pentru if-urile din replaceOverlappingInterval, și metoda o vom numi addIfNonEmpty
  4. Verificați dacă rulează.
  5. Dati push modificărilor pe Github prin CLI.

Posibile probleme:

  • Nu este configurat SDK (File→Project Structure→Project și alegeți SDK-ul)
  • Nu este configurat source folder-ul (File→Project Structure→Modules alegeți directorul și marcați-l ca Sources)

Resurse utile

Referințe

icalc/laboratoare/laborator-05.txt · Last modified: 2022/04/18 22:50 by liviu.mitruta
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0