Doporučené postupy v programování – 2. úkol: Návrh knihovny

Nezapomeňte vytvořit dvojice do 25. 3. 2018. Viz submission systém.

Nezapomeňte odevzdat tento úkol do 2. 4. 2018 (přes submission systém).

U téměř každé složitější aplikace se dříve či později vyskytne potřeba mít možnost uložit konfiguraci do souboru, aby bylo možné ji při příštím spuštění načíst a zbavit tak uživatele nutnosti aplikaci opakovaně konfigurovat. V různých počítačových "světech" se externalizace konfigurace řeší různými způsoby, počínaje jednoduchými soubory s hodnotami v přesném pořadí na jednotlivých řádcích, přes různě strukturované textové a binární soubory, systémové databáze typu GConf nebo Windows Registry, až po klasické i méně klasické databáze v případě webových aplikací.

Cílem druhého úkolu je vyzkoušet si návrh, implementaci a dokumentaci jednoduché knihovny na práci s konfiguračními soubory ve formátu často označovaném jako Windows .ini file. Není to nijak zvlášť těžký úkol, ale není také úplně triviální – tak akorát, aby se knihovna dala napsat celá úplně od nuly a abyste si mohli dát záležet na dobrém návrhu rozhraní a vnitřní architektury, aby se požadavky na knihovnu kladené daly snadno naimplementovat. Samozřejmostí by měla být snaha o co nejkvalitnější kód držící se zásad vysvětlovaných na přednáškách a cvičeních, včetně dokumentace pro případné uživatele knihovny.

Specifikace obecného formátu .ini souboru

Komentář
veškerý text od středníku do konce řádku
Identifikátor
řetězec znaků z množiny { a-z, A-Z, 0-9, _, ~, -, ., :, $, mezera } začínající znakem z množiny { a-z, A-Z, . , $, : }
Sekce
část konfiguračního souboru označená identifikátorem
  • sekce začíná hlavičkou, kterou tvoří identifikátor sekce v hranatých závorkách na začátku řádku, např. [Defaults]
  • sekce je ukončena začátkem následující sekce nebo koncem souboru
  • sekce nemůže být rozdělena na více částí, tj. hlavička sekce může být v souboru uvedena pouze jednou
  • sekce obsahuje 0 a více konfiguračních voleb
Volba
uspořádáná dvojice (identifikátor, hodnota) v rámci nějaké sekce
  • volba je v souboru zapsána ve formátu identifikátor=hodnota
  • mezery (white space) na začátu a na konci identifikátoru nebo volby se ignorují, pokud jim nepředchází znak '\'
  • mezery mezi slovy (posloupnost znaků neobsahující mezeru) jsou součástí identifikátoru nebo hodnoty
  • hodnota je reprezentována jedním nebo více elementy stejného typu oddělených čárkou (,) nebo dvojtečkou (:), v rámci jedné hodnoty ale vždy buď pouze (,) nebo pouze (:)
Element
text reprezentující část nebo celou hodnotu spojenou s identifikátorem volby
  • text elementu může obsahovat odkaz na hodnotu volby v nějaké skupině
    • formát odkazu je ${sekce#volba}
    • hodnota se vkládá jako textová náhrada, t.j. případné speciální znaky a příkazy se musí po vložení interpretovat
    • odkaz se neinterpretuje pokud znaku $ předchází znak '\'
  • element může být typu boolean, signed, unsigned, float, enum, nebo string
    • element typu boolean může nabývat hodnot z množiny { 0, f, n, off, no, disabled } pro ohodnocení false nebo hodnot z množiny { 1, t, y, on, yes, enabled } pro ohodnocení true
    • element typu signed může nabývat hodnot v rozsahu určeném 64-bitovou reprezentací celých čísel ve dvojkovém doplňku
    • element typu unsigned může nabývat hodnot v rozsahu určeném 64-bitovou reprezentací přirozených čísel
    • elementy typu signed a unsigned specifikují čísla v desítkové soustavě; reprezentace v šestnáctkové, osmičkové, nebo dvojkové soustavě je nutno uvést s prefixem '0x', '0', respektive '0b'
    • element typu float může nabývat hodnot v rozsahu určeném 64-bitovou reprezentací čísel v plovoucí řádové čárce (IEEE 754), zapsaných v obvyklém formátu
    • element typu enum může nabývat hodnot z předem definované množiny řetězců
    • element typu string může obsahovat libovolné znaky s výjimkou ',', ':' a ';', kde je třeba je uvést znakem '\'

Příklad souboru, který vyhovuje uvedené specifikaci


[Sekce 1]
; komentar
Option 1 = value 1                     ; volba 'Option 1' ma hodnotu 'value 1'
oPtion 1    =  \ value 2\ \ \          ; volba 'oPtion 1' ma hodnotu ' value 2   ', 'oPtion 1' a 'Option 1' jsou ruzne volby

[$Sekce::podsekce]
Option 2=value 1:value 2:value 3       ; volba 'Option 2' je seznam hodnot 'value 1', 'value 2' a 'value 3'
Option 3 =value 1, ${Sekce 1#Option 1} ; volba 'Option 3' je seznam hodnot 'value 1' a 'value 1'
Option 4= v1,${$Sekce::podsekce#Option 3},v2 ; volba 'Option 4' je seznam hodnot 'v1', 'value 1', 'value 1', 'v2'
Option 5= v1, v2:v3                    ; volba 'Option 5' je seznam hodnot 'v1' a 'v2:v3', nebo 'v1, v2' a 'v3' podle zvoleneho oddelovace

[Cisla]
cele = -1285
cele_bin = 0b01101001
cele_hex = 0x12ae,0xAc2B
cele_oct = 01754

float1 = -124.45667356
float2 = +4.1234565E+45
float3 = 412.34565e45
float4 = -1.1245864E-6

[Other]
bool1 = 1
bool2 = on
bool3=f


Funkční požadavky

Vaše knihovna musí uživateli umožnit pohodlně provádět následující operace:

  • Specifikovat konkrétní formát konfiguračního souboru.
    Pro každou sekci by mělo být definováno:
    • identifikátor,
    • zda je sekce povinná a
    • popis sekce shrnující druh voleb, které sdružuje.
    Pro každou volbu v sekci by mělo být definováno:
    • identifikátor,
    • zda je volba v sekci povinná,
    • zda je hodnota tvořena jedním elementem nebo seznemam elementů,
    • typ elementu a omezení na jejich hodnoty (interval pro čísla, výčet položek pro enum),
    • implicitní hodnota pokud je volba nepovinná a
    • popis volby a přípustné hodnoty
  • Načíst konfigurační soubor a ověřit zda vyhovuje zadanému formátu v jednom ze dvou režimů:
    1. striktní (strict) – obsah souboru musí přesně odpovídat definici a jakákoliv odchylka má za následek chybu
    2. uvolněný (relaxed) – neznámé sekce a volby se načtou jako řetězce, u známých voleb a sekcí probíhá plná kontrola
  • Umožnit pohodlný přístup k načtené konfiguraci včetně změny hodnot voleb
  • Zapsat konfiguraci zpět do souboru tak, aby i po provedených změnách byl nový soubor podobný původnímu, např. by se neměly do souboru zapisovat volby s default hodnotami, pokud nebyly v původním soubouru uvedeny
  • Zapsat default konfiguraci všech sekcí a voleb do souboru, včetně dokumentačních komentářů
  • Umožnit načtení/zápis konfigurace z/do bufferu v paměti (řetězec, stream, ...) pro testovací účely

Některé požadavky nejsou specifikovány úplně přesně a jistě by se daly najít nějaké další funkce, které by knihovna mohla podporovat. Pokud je něco definováno nejasně, použijte vlastní úsudek k dodefinování a svoje rozhodnutí zdokumentujte. Pokud jste kreativní, může vaše knihovna implementovat i další funkce – určitě to oceníme, ale pouze pokud bude knihovna splňovat všechny výše uvedené požadavky.

Části řešení

Řešení se skládá ze samotné knihovny (tj. zdrojových souborů včetně souborů pro přeložení z příkazového řádku), zdůvodnění návrhu a dokumentace.

Zdrojový kód může být v některém z následujících jazyků: Java, C, C++, C#, po dohodě se cvičícími i jiné (např. PHP, Python). V dokumentaci uveďte přesnou verzi jazyka, kterou používáte a v případě C++ i kompilátor. Omezení na použité verze neklademe, spoléháme na vaši rozumnost :-) Zdrojový kód musí být bez jakýchkoliv úprav začlenitelný do většího programu.

Každá slušná knihovna by měla být přenositelná. Z tohoto důvodu, a také z důvodu jednoduššího hodnocení, knihovna MUSÍ (nutná podmínka) obsahovat také soubory nutné pro překlad mimo grafické vývojové prostředí, specificky pro tyto jazyky:

  • C/C++ - knihovna musí jít přeložit v gcc a musíte dodat Makefile (pro GNU Make)
  • Java - knihovna musí obsahovat Makefile (pro GNU Make) nebo build.xml pro Ant
  • C# - knihovna musí jít přeložit v Mono a musíte dodat Makefile (pro GNU Make)
Požadované nástroje jsou k dispozici v labu nebo volně k dispozici jako open-source. V ostatních případech se očekává použití dávkového nástroje obvyklého pro zvolený jazyk a prostředí.

Zdůvodnění návrhu by mělo tvořit 1 - 2 strany textu, stručně popisujících důležitá rozhodnutí, která jste při návrhu knihovny učinili a jejich odůvodnění. Váš návrh by měl vycházet z use-cases, tj. typických scénářů, které by měl být uživatel s použitím knihovny schopen realizovat. Nezapomeňte, že zdůvodnění návrhu NENÍ programátorská nebo uživatelská dokumentace, ale opravdu text zdůvodňující proč jste např. zvolili/nezvolili dědičnost, výjimky, chybové kódy, metody v rozhraní, apod.

Dokumentace by měla mít dvě části – stručnou ukázku, jak lze knihovnu používat (tj. 2 - 3 příklady typických situací) a pak dokumentaci jednotlivých součástí kódu. Použijte přitom v maximální míře standardní dokumentační systém jazyka, ve kterém jste rozhraní navrhli; vytvořte v něm dokumentaci ke všem prvkům (třídám, metodám, ...) vašeho rozhraní a pokud to dokumentační systém umožňuje, vygenerujte dokumentaci v HTML či jiném vhodném formátu. Při tvorbě dokumentace se držte konvencí platných pro daný jazyk a dokumentační systém. U generované dokumentace nezapomeňte na hlavní stránku, která je implicitně prázdná – to je ideální prostor pro přehledový text, který uživateli knihovnu představí a ukáže mu příklady běžného použití knihovny. Důraz bude kladen hlavně na popis následujících věcí:

  • Význam konstant, proměnných a výčtových typů
  • Zodpovědnost a kontrakty tříd a metod
  • Význam a platné/možné hodnoty parametrů a návratových hodnot
  • Chybové stavy (např. vyhazované výjimky)

Řešení odevzdáte opět v submission systému. Odevzdávat budete tyto soubory: Zdrojové kódy a buildovací skripty v jedné složce, dokumentace v další a zdůvodnění návrhu jako jeden soubor ve formátu PDF nebo plain text (.txt) v kódovaní UTF-8. Tyto soubory zabalte některým běžným komprimačním nástrojem (ZIP, .tar.gz apod.). Ujistěte se, že v balíčku nejsou žádné binární soubory – .class, .exe, .obj, .dll a podobné. Pokud budete pracovat ve Visual Studiu, odstraňte také všechny pomocné soubory (databáze symbolů apod.) Solution a project file samozřejmě pošlete. Dále vás prosíme, abyste v žádném případe balíčky nezaheslovali.

Cílem těchto požadavků je zajistit bezproblémové zpracování a hodnocení domácích úkolů, k čemuž výrazně příspívá jednotná "štábní kultura" odevzdaných řešení.

Hodnocení

Na řešení budou hodnoceny následující věci:

  • Splnění všech funkčních požadavků
  • Pohodlnost práce s rozhraním pro uživatele (tj. návrh tříd a metod, vhodnost názvů, apod.)
  • Kvalita implementace (čistota a robustnost kódu)
  • Zdůvodnění návrhu
  • Úroveň a přesnost dokumentace

Přesná bodová stupnice není stanovena; obecně lze říct, že budou strhávány body za "prohřešky" ve výše uvedených kategoriích.

Výjimku představuje penalizace za opožděné odevzdání ve výši 8 bodů za každý den zpoždění.

Návodné otázky

  • Jaké třídy bude knihovna obsahovat? Jaké budou jejich zodpovědnosti?
  • Umožníte uživateli rozšiřovat knihovnu nebo definovat vlastní chování? Jaké rozhraní a jaké mechanizmy k tomu použijete?
  • Jak bude uživatel definovat jednotlivé sekce a volby, a jejich parametry? Nabízí se například nějaká datová struktura, řetězec s formátem či výstavba pomocí volání metod.
  • Jak se uživatel dostane k sekcím a volbám načtené konfigurace? Bude jen procházet seznam sekcí a voleb, bude mít možnost se dotázat přímo na přítomnost některých voleb a jejich hodnot, nebo mu umožníte reagovat na konkrétní volby ve formě callbacků? Jak bude přistupovat k hodnotám tvořeným seznamem elementů? Jaký význam bude mít hodnota typu enum umožňující zadat seznam výčtových položek?
  • Jak bude knihovna provádět kontrolu sekcí, voleb a jejich typů? Explicitně či implicitně? Použijete výjimky nebo chybové kódy? Pokud výjimky, jakou strukturu výjimek definujete? Použijete nějaké standardní výjimky implementačního jazyka? Bude knihovna umět rovnou vypsat hlášení uživateli programu?

Tipy

  • Podívejte se na existující knihovny a inspirujte se. Zkuste napravit chyby těchto knihoven (popřemýšlejte například, co vás na nich někdy v minulosti štvalo).
  • Vezměte nějaký svůj program, ve kterém by bylo vhodné externalizovat konfiguraci a zkuste ho alespoň v myšlenkách přepsat do vašeho API. Kód vám napoví, zda jste se vydali správným směrem.
  • Pokud nemáte takový program k dispozici, zkuste si alespoň vymyslet několik use-casů – třeba na základě programů, které běžně používáte.

Recommended Programming Practices – 2nd assignment: Design of a library

Do not forget to make pairs until 25. 3. 2018. Viz submission system.

Do not forget to submit this assignment until 2. 4. 2018 (via submission system).

Almost every complex application needs, sooner or later, a way to store its configuration so that it can be loaded at every startup and the user can be free of repeated configuration. In different computer "worlds" this externalization of configuration is handled differently, starting with simple files with values in strict order on individual lines, through diversely structured textual and binary files, system databases such as GConf or Windows Registry, ending with classical and less classical databases in case of web applications.

The goal of the second assignment is to get hands on designing, implementing and documenting a simple library that provides means to work with configuration files formated as so called Windows .ini file. This is not a hard problem, but neither is it a trivial one. The complexity is sufficient so the library can be written from scratch and so you can pay attention to good design of the API and the inner architecture. The requirements of the library can be easily implemented. It should be obvious that you are supposed to write high quality code that complies to principles discussed on lectures and labs, including the documentation for the potential users of the library.

Specification of a general .ini file format

Commentary
text that starts with semi-colon until the end of the line
Identifier
string of characters from the set { a-z, A-Z, 0-9, _, ~, -, ., :, $, space } beginning with a character from the set { a-z, A-Z, . , $, : }
Section
part of the configuration file labeled by identifier
  • a section starts with header, which is made by identifier of the section in brackets at the beginning of a line, e.g. [Defaults]
  • a section ends with a beginning of another section or with the end of the file
  • a section cannot be divided into parts, i.e. the header of the section can be only once in the file
  • a section contains 0 or more configuration options
Option
ordered pair (identifier, value) within some section
  • an option is written as identifier=value
  • a white space in the beginning and at the end of an identifier are ignored, unless they are escaped by '\'
  • spaces between words (sequence of characters not containing a space) are part of the identifier or the value
  • a value is represented by one or more elements of the same type separated by a comma (,) or by a colon (:), within one value but always only either (,) or (:)
Element
a text representing partial or complete value connected to an identifier of an option
  • text of an element can contain reference to a value of an option in some group
    • the format of a reference is ${section#option}
    • a value is inserted as textual replacement, i.e. incidental special characters and commands has to be interpreted after being inserted
    • a reference is not interpreted if the $ sign is escaped by '\'
  • element can by one of these types boolean, signed, unsigned, float, enum, or string
    • element of type boolean can be a value from the set { 0, f, n, off, no, disabled } for the valuation false or a value from the set { 1, t, y, on, yes, enabled } for the valuation true
    • element of type signed can be a value from the range defined by 64-bit representation of integers in the two's complement
    • element of type unsigned can be a value from the range defined by 64-bit representation of integers
    • elements of type signed and unsigned specify numbers in decimal system; representation in hexadecimal, octal and binary system has to be prefixed by '0x', '0' and '0b' accordingly
    • elements of type float can be a value from the range specified by 64-bit representation of floating point numbers (IEEE 754), written in usual format
    • elements of type enum can be a value from defined set of strings
    • element of type string can contain arbitrary characters with the exception of ',', ':' and ';', which needs to be escaped by '\'

Example of a file that satisfies the given specification


[Section 1]
; comment
Option 1 = value 1                     ; the option 'Option 1' has the value 'value 1'
oPtion 1    =  \ value 2\ \ \          ; the option 'oPtion 1' has the value ' value 2   ', 'oPtion 1' and 'Option 1' are different options

[$Section::subsection]
Option 2=value 1:value 2:value 3       ; the option 'Option 2' is a list of values 'value 1', 'value 2' and 'value 3'
Option 3 =value 1, ${Section 1#Option 1} ; the option 'Option 3' is a list of values 'value 1' and 'value 1'
Option 4= v1,${$Section::subsection#Option 3},v2 ; the option 'Option 4' is a list of values 'v1', 'value 1', 'value 1', 'v2'
Option 5= v1, v2:v3                    ; the option 'Option 5' is a list of values 'v1' and 'v2:v3', or 'v1, v2' and 'v3' depending on the selected separator

[Numbers]
integer = -1285
integer_bin = 0b01101001
integer_hex = 0x12ae,0xAc2B
integer_oct = 01754

float1 = -124.45667356
float2 = +4.1234565E+45
float3 = 412.34565e45
float4 = -1.1245864E-6

[Other]
bool1 = 1
bool2 = on
bool3=f


Functional requirements

Your library has to allow the user comfortably use the following operations:

  • To specify concrete format of a configuration file. For each section there should be defined:
    • identifier,
    • whether the section is mandatory and
    • description of a section summarizing option types that it contains.
    For each option in a section there should be defined:
    • identifier,
    • whether an option is mandatory in the section,
    • whether a value consists of one element or a list of elements,
    • a type of the element and constraints of its values (interval for numbers, enumeration of items for enum),
    • default value if the option is optional and
    • description of the option and allowed values.
  • Load a configuration file and verify whether it comply with the given format in one of these two modes:
    1. strict – the content of a file has to exactly match the definition and every deviation causes an error
    2. relaxed – unknown sections and options loads as strings, for known options and sections there is full checking
  • Allow convenient access to loaded configuration including changing values for the options.
  • Write the configuration back to the file so that the new file is similar to the old one after the changes are made, e.g. the new file should not contain options with default values if these options were not defined in the original file.
  • Write a default configuration of all sections and options including documentation comments.
  • Allow writing and reading of a configuration to/from a buffer in memory (string, stream, ...) for testing purposes.

Some requirements are not defined thoroughly and certainly there can be found another functions that the library could support. If there is anything defined vaguely, use your judgment and define it properly and document your decision. If you are creative, your library can implement additional functions – we will appreciate that, but only in case that the library will satisfy all of the requirements above.

Solution

The solution consists of the library (i.e. source files) including files for compilation from command line, justification of the design and documentation.

Source code can be written in either Java, C, C++, C#, after consultation it may be possible to use a different language (e.g. PHP, Python). In the documentation state the exact version of chosen language, in case of C++ also the compiler. We do not limit any version of a language, we trust you being reasonable :-) Source code has to be, without any adjustments, integratable into a bigger program.

Every decent library should be portable. For that reason, and also because of easier evaluation, the library MUST (a necessary condition) contain files necessary for its compilation from command line, especially for these languages:

  • C/C++ - the library has to be compilable in gcc and has to contain Makefile (for GNU Make)
  • Java - the library has to contain Makefile (for GNU Make) or build.xml for Ant
  • C# - the library has to be compilable in Mono and has to contain Makefile (for GNU Make)
Required tools are at your disposal in lab or freely available as open-source. In other cases it is expected to use batch tools common for the chosen language and environment.

Description of your design should take about 1 - 2 pages of text, briefly describing important decisions, that you made while designing the library and justification of those decisions. Your design should come out from the use-cases, i.e. typical scenarios which can be realized using the library. Do not forget that description of your design is NOT program or user documentation, but truly a text justifying why have you for example decided to use/not use inheritance, exceptions, error codes, methods in API, and so on.

The documentation should have two parts – brief illustration of usage (i.e. 2 - 3 examples of typical scenarios) and documentation of individual code elements. Use the standard documentation system of the chosen language into the utmost extent. Write documentation for all the elements (classes, methods, ...) of the API and if the documentation system allows, generate documentation in HTML or different suitable format. When writing the documentation follow the conventions valid for given language and documentation system. For the generated documentation do not forget the main page which is empty by default – this is the ideal place for overview text which introduces the library to the user and shows examples of common usage. A description of the following things is necessary:

  • Meaning of constants, variables and enums
  • Responsibility and contracts of classes and methods
  • Meaning and valid values of arguments and return values
  • Error states (e.g. thrown exceptions)

Submit your solution via the submission system. You will submit these files: Source codes and build scripts in one folder, documentation in another and description of your design as one file in PDF or as plain text (.txt) encoded in UTF-8. These files pack using any ordinary compress tool (ZIP, .tar.gz etc.). Ensure there are no binary files in the package – .class, .exe, .obj, .dll, etc. If you work in Visual Studio, remove also all auxiliary files (database of symbols etc.). Solution and project file submit as well of course. We are explicitly asking you not to encrypt the packages.

The goal of these requirements is to ensure seamless processing and evaluation of your assignments and it significantly helps having united structure of submitted solutions.

Evaluation

There will be evaluated the following things:

  • Fulfillment of all functional requirements
  • Comfort of using the API (i.e. design of classes and methods, whether the names are adequate. etc.)
  • Quality of the implementation (clean and robust code)
  • Justification of the design
  • Level and accuracy of documentation

The exact scale of points is not determined; generally we can say that points will be taken for the offense against the above stated categories.

An exception is penalties for late submission, which makes 8 points for each day after the deadline

Guide questions

  • What classes will the library contain? What will be their responsibility?
  • Will you allow the user to extend the library or to define custom behavior? What API and what mechanisms would you use for it?
  • How will the user define individual sections, options and their parameters? Is there, for example, some convenient data structure, string with format or building using method calls?
  • How will the user access the sections and options of the loaded configuration? Will the user just browse the list of sections and options, will he/she be able to access them directly, or will he/she be notified about the values in some method callbacks? How will the user access the values in list of elements? What will be the meaning of an enum values used in list of enum elements?
  • How will the library check the sections, options and their types? Explicitly or implicitly? Will you use exceptions or error codes? If you use exceptions, what structure of exceptions will you define? Will you use some standard exceptions of the chosen language? Will the library be able to directly output messages to user of the program?

Tips

  • Take a look at existing libraries for inspiration. Try to fix defects of these libraries (think, for example, what bothered you when you used them).
  • Take one of your programs in which it would be suitable to externalize the configuration and try, at leas in your mind, to rewrite it using your API. The code will suggest you, whether you are going the correct direction.
  • If you don't have such a program, try at leas think of few use cases – perhaps based on programs that you usually use.
Logo of Faculty of Mathematics and Physics
  • Phone: +420 951 554 267, +420 951 554 236
  • Email: info<at-sign>d3s.mff.cuni.cz
  •  
  • How to find us?
Modified on 2018-04-24